C++ 中的对象是否应该删除自身?

发布于 2024-07-12 19:42:10 字数 549 浏览 7 评论 0原文

过去 4 年我一直在使用 C#,因此我对 C++ 中当前的最佳实践和常见设计模式很感兴趣。 考虑以下部分示例:

class World
{
public:
    void Add(Object *object);
    void Remove(Object *object);
    void Update();
}

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this;
        }
    }
}

这里我们有一个世界,负责管理一组对象并定期更新它们。 火是一个可以在许多不同情况下添加到世界中的对象,但通常是由世界中已有的另一个对象添加到世界中的。 火是唯一知道它何时烧毁的对象,所以目前我让它自行删除。 引发火灾的物体可能已不存在或不再相关。

这是明智的做法还是有更好的设计可以帮助清理这些物体?

I've spent the last 4 years in C# so I'm interested in current best practices and common design patterns in C++. Consider the following partial example:

class World
{
public:
    void Add(Object *object);
    void Remove(Object *object);
    void Update();
}

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this;
        }
    }
}

Here we have a world responsible for managing a set of objects and updating them regularly. Fire is an an object that might be added to the world under many different circumstances but typically by another object already in the world. Fire is the only object that knows when it has burned out so currently I have it deleting itself. The object that created the fire is likely no longer in existence or relevant.

Is this a sensible thing to do or is there a better design that would help clean up these objects?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

假装爱人 2024-07-19 19:42:11

它当然适用于由 new 创建的对象,并且当 Update 的调用者被正确告知该行为时。 但我会避免它。 就你而言,所有权显然属于世界,所以我会让世界删除它。 该对象不会创建自身,我认为它也不应该删除自身。 如果您在对象上调用“更新”函数,但突然间该对象不再存在,而 World 本身不做任何事情(除了将其从其列表中删除之外 - 但在另一个框架中),这可能会非常令人惊讶!调用的代码对对象的更新不会注意到这一点)。

关于此的一些想法

  1. 添加对 World 的对象引用列表。 该列表中的每个对象都等待删除。 这是一种常见的技术,在 wxWidgets 中用于已关闭但仍可能接收消息的顶级窗口。 在空闲时间,当处理完所有消息后,处理挂起列表,并删除对象。 我相信 Qt 遵循类似的技术。
  2. 告诉世界该对象想要被删除。 世界将得到适当的通知,并将处理任何需要做的事情。 也许类似于 deleteObject(Object&)
  3. 向每个对象添加一个 shouldBeDeleted 函数,如果该对象希望被其所有者删除,该函数将返回 true。

我更喜欢选项 3。世界会调用更新。 之后,它会查看该对象是否应该被删除,并且可以这样做 - 或者如果它愿意,它会通过手动将该对象添加到待删除列表来记住这一事实。

当您无法确定何时以及何时不能访问对象的函数和数据时,这是一件很痛苦的事情。 例如,在wxWidgets中,有一个wxThread类,它可以以两种模式运行。 其中一种模式(称为“可分离”)是,如果其主函数返回(并且应该释放线程资源),则它会删除自身(以释放 wxThread 对象占用的内存),而不是等待线程的所有者对象调用等待或加入函数。 然而,这会导致严重的头痛。 你永远不能调用它的任何函数,因为它在任何情况下都可能被终止,并且你不能不使用 new 创建它。 很多人告诉我他们非常不喜欢它的这种行为。

引用计数对象的自删除是有味道的,恕我直言。 让我们比较一下:

// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances. 
class Bitmap {
    ~Bitmap() {
        if(bmp->dec_refcount() == 0) {
            // count drops to zero => remove 
            // ref-counted object. 
            delete bmp;
        }
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount();
    int inc_refcount();

};

与自删除引用计数对象进行比较:

class Bitmap {
    ~Bitmap() {
        bmp->dec_refcount();
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount() {
        int newCount = --count;
        if(newCount == 0) {
            delete this;
        }
        return newCount;
    }
    int inc_refcount();
};

我认为第一个要好得多,而且我相信设计良好的引用计数对象不会“删除这个”,因为它增加了耦合:使用引用计数数据的类必须知道并记住数据会删除自身,这是减少引用计数的副作用。 请注意“bmp”如何可能成为 ~Bitmap 析构函数中的悬空指针。 可以说,不这样做“删除这个”在这里要好得多。

回答类似问题“删除这个有什么用”

It certainly works for objects created by new and when the caller of Update is properly informed about that behavior. But i would avoid it. In your case, the ownership clearly is at the World, so i would make the world delete it. The object does not create itself, i think it should also not delete itself. Can be very surprising if you call a function "Update" on your object, but then suddenly that object is not existing anymore without World doing anything out of itself (apart from Removing it out of its list - but in another frame! The code calling Update on the object will not notice that).

Some ideas on this

  1. Add a list of object references to World. Each object in that list is pending for removal. This is a common technique, and is used in wxWidgets for toplevel windows that were closed, but may still receive messages. In idle time, when all messages are processed, the pending list is processed, and objects are deleted. I believe Qt follows a similar technique.
  2. Tell the world that the object wants to be deleted. The world will be properly informed and will take care of any stuff that needs to be done. Something like a deleteObject(Object&) maybe.
  3. Add a shouldBeDeleted function to each object which returns true if the object wishes to be deleted by its owner.

I would prefer option 3. The world would call Update. And after that, it looks whether the object should be deleted, and can do so - or if it wishes, it remembers that fact by adding that object to a pending-removal list manually.

It's a pain in the ass when you can't be sure when and when not to be able to access functions and data of the object. For example, in wxWidgets, there is a wxThread class which can operate in two modes. One of these modes (called "detachable") is that if its main function returns (and the thread resources should be released), it deletes itself (to release the memory occupied by the wxThread object) instead of waiting for the owner of the thread object to call a wait or join function. However, this causes severe headache. You can never call any functions on it because it could have been terminated at any circumstances, and you can not have it created not with new. Quite some people told me they very much dislike that behavior of it.

The self deletion of reference counted object is smelling, imho. Let's compare:

// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances. 
class Bitmap {
    ~Bitmap() {
        if(bmp->dec_refcount() == 0) {
            // count drops to zero => remove 
            // ref-counted object. 
            delete bmp;
        }
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount();
    int inc_refcount();

};

Compare that with self-deleting refcounted objects:

class Bitmap {
    ~Bitmap() {
        bmp->dec_refcount();
    }
    BitmapData *bmp;
};

class BitmapData {
    int dec_refcount() {
        int newCount = --count;
        if(newCount == 0) {
            delete this;
        }
        return newCount;
    }
    int inc_refcount();
};

I think the first is so much nicer, and i believe well designed reference counted objects do not do "delete this", because it increases coupling: The class using the reference counted data has to know and remember about that the data deletes itself as a side-effect of decrementing the reference-count. Note how "bmp" becomes possibly a dangling pointer in ~Bitmap's destructor. Arguably, not doing that "delete this" is much nicer here.

Answer to a similar question "What is the use of delete this"

美人如玉 2024-07-19 19:42:11

其他人提到了“删除此”的问题。 简而言之,既然“世界”管理着火的创造,那么它也应该管理着火的删除。

另一个问题是,世界末日是否会伴随着仍在燃烧的大火。 如果这是扑灭火灾的唯一机制,那么最终可能会引发孤立火灾或垃圾火灾。

对于您想要的语义,我将在您的“Fire”类(或对象,如果适用)中有一个“活动”或“活动”标志。 然后,世界有时会检查其物品清单,并清除不再活跃的物品。

--

还要注意的是,您所编写的代码具有从 Object 私有继承的 Fire,因为这是默认设置,尽管它比公共继承要少得多。 您可能应该明确指定继承类型,并且我怀疑您确实想要公共继承。

Others have mentioned problems with "delete this." In short, since "World" manages the creation of Fire, it should also manage its deletion.

One other problem is if the world ends with a fire still burning. If this is the only mechanism through which a fire can be destroyed, then you could end up with an orphan or garbage fire.

For the semantics you want, I would have an "active" or "alive" flag in your "Fire" class (or Object if applicable). Then the world would on occasion check its inventory of objects, and get rid of ones that are no longer active.

--

One more note is that your code as written has Fire privately inheriting from Object, since that is the default, even though it is much less common than public inheiritance. You should probably make the kind of inheritance explicit, and I suspect you really want public inheritiance.

断肠人 2024-07-19 19:42:11

除非有必要或以非常简单的方式使用,否则“删除此”通常不是一个好主意。 在您的情况下,世界看起来可以在删除对象时删除该对象,除非存在我们没有看到的其他依赖项(在这种情况下,您的删除调用将导致错误,因为它们没有被通知)。 将所有权保留在对象之外可以使对象更好地封装并且更稳定:对象不会仅仅因为选择删除自身而在某个时刻变得无效。

It's not generally a good idea to do a "delete this" unless necessary or used in a very straightforward way. In your case it looks like the World can just delete the object when it is removed, unless there are other dependencies we're not seeing (in which case your delete call will cause errors since they aren't notified). Keeping ownership outside your object allows your object to be better encapsulated and more stable: the object won't become invalid at some point simply because it chose to delete itself.

娇妻 2024-07-19 19:42:11

我不认为对象删除自身有什么本质上的错误,但另一种可能的方法是让世界负责删除对象作为 ::Remove 的一部分(假设所有已删除的对象也被删除。)

I don't this there's anything inherently wrong with an object deleting itself, but another possible method would be to have the world be responsible for deleting objects as part of ::Remove (assuming all Removed objects were also deleted.)

破晓 2024-07-19 19:42:11

这是一个相当常见的引用计数实现,我之前已经成功使用过。

但是,我也看到它崩溃了。 我希望我能记得坠机的情况。 @abelenky 是我见过它崩溃的地方。

它可能是您进一步子类化 Fire 的地方,但无法创建虚拟析构函数(见下文)。 当您没有虚拟析构函数时,Update() 函数将调用 ~Fire() 而不是相应的析构函数 ~Flame() >。

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this; //Make sure this is a virtual destructor.
        }
    }
};

class Flame: public Fire
{
private: 
    int somevariable;
};

That's a fairly common reference counting implementation and I've used that successfully before.

However, I've also seen it crash. I wish I could remember the circumstances for the crash. @abelenky is one place I have seen it crash.

It might have been where you further subclass Fire, but fail to create a virtual destructor (see below). When you don't have a virtual destructor, the Update() function will call ~Fire() instead of the appropriate destructor ~Flame().

class Fire : Object
{
public:
    virtual void Update()
    {
        if(age > burnTime)
        {
            world.Remove(this);
            delete this; //Make sure this is a virtual destructor.
        }
    }
};

class Flame: public Fire
{
private: 
    int somevariable;
};
久伴你 2024-07-19 19:42:11

删除这个是非常危险的。 这没有什么“问题”。 这是合法的。 但使用它时可能会出现很多问题,因此应谨慎使用。

考虑这样的情况:某人打开了该对象的指针或引用,然后它自行删除。

在大多数情况下,我认为对象生命周期应该由创建它的同一个对象来管理。 它只是让人们更容易知道破坏发生在哪里。

delete this is very risky. THere's nothing "wrong" with it. It's legal. But there are so many things that can go wrong when you use it that it should be used sparingly.

Consider the case where someone has open pointers or references to the object, and then it goes and deletes itself.

In most cases, i think the object lifetime should be managed by the same object that creates it. It just makes it easier to know where destruction occurs.

染年凉城似染瑾 2024-07-19 19:42:10

在 C++ 中对象删除自身并没有什么问题,大多数引用计数的实现都会使用类似的东西。 然而,它被视为一种有点深奥的技术,您需要密切关注所有权问题。

There is nothing really wrong with objects deleting themselves in C++, most implementations of reference counting will use something similar. However, it is looked on as a slightly esoteric technique, and you will need to keep a really close eye on the ownership issues.

毁我热情 2024-07-19 19:42:10

如果对象没有用new分配和构造,删除将会失败,并且可能导致程序崩溃。 此构造将阻止您在堆栈上或静态地实例化该类。 就您而言,您似乎正在使用某种分配方案。 如果你坚持这样做,这可能是安全的。 但我当然不会这么做。

The delete will fail and may crash the program if the object was not allocated and constructed with new. This construct will prevent you from instantiating the class on the stack or statically. In your case, you appear to be using some kind of allocation scheme. If you stick to that, this might be safe. I certainly wouldn't do it, though.

陌路黄昏 2024-07-19 19:42:10

我更喜欢更严格的所有权模式。 世界应该拥有其中的物体并负责清理它们。 要么让 world remove 执行此操作,要么让 update(或其他函数)返回一个指示应删除对象的值。

我还猜测,在这种类型的模式中,您将需要对对象进行引用计数,以避免出现悬空指针。

I prefer a stricter ownership model. The world should own the objects in it and be responsible for cleaning them up. Either have world remove do that or have update (or another function) return a value that indicates that an object should be deleted.

I am also guessing that in this type of pattern, you are going to want to reference count your objects to avoid ending up with dangling pointers.

挽心 2024-07-19 19:42:10

您已将 Update 设为虚函数,这表明派生类可以重写 Update 的实现。 这带来了两大风险。

1.) 重写的实现可能会记得执行World.Remove,但可能会忘记delete this。 内存泄露了。

2.) 重写的实现调用基类Update,它执行delete this,但随后继续进行更多工作,但 this 指针无效。

考虑这个例子:

class WizardsFire: public Fire
{
public:
    virtual void Update()
    {
        Fire::Update();  // May delete the this-object!
        RegenerateMana(this.ManaCost); // this is now invaild!  GPF!
    }
}

You have made Update a virtual function, suggesting that derived classes may override the implementation of Update. This introduces two big risks.

1.) An overridden implementation may remember to do a World.Remove, but may forget the delete this. The memory is leaked.

2.) The overridden implementation calls the base-class Update, which does a delete this, but then proceeds with more work, but with an invalid this-pointer.

Consider this example:

class WizardsFire: public Fire
{
public:
    virtual void Update()
    {
        Fire::Update();  // May delete the this-object!
        RegenerateMana(this.ManaCost); // this is now invaild!  GPF!
    }
}
唱一曲作罢 2024-07-19 19:42:10

这样做的问题是,您实际上是在对象和 World 类之间创建了隐式耦合。

如果我尝试在 World 类之外调用 Update() ,会发生什么? 我可能最终会删除该对象,但我不知道为什么。 看来责任严重混淆了。 当您在编写此代码时未曾想到的新情况下使用 Fire 类时,这将导致问题。 如果要从多个位置删除对象会发生什么情况? 也许它应该从世界、当前地图和玩家的库存中删除? 您的更新功能会将其从世界中删除,然后删除该对象,下次地图或库存尝试访问该对象时,就会发生坏事。

一般来说,我认为 Update() 函数删除它正在更新的对象是非常不直观的。 我还想说,对象删除自身是不直观的。
该对象更可能应该有某种方式来触发一个事件,表明它已完成燃烧,任何感兴趣的人现在都可以对此采取行动。 例如,将其从世界上删除。 要删除它,请考虑所有权。

谁拥有该对象? 世界? 这意味着只有世界才能决定物体何时死亡。 只要世界对该对象的引用比其他对该对象的引用持续时间就可以了。
你认为该物体拥有自己吗? 那有什么意思? 当对象不再存在时应该删除该对象吗? 没有道理。

但如果没有明确定义的单一所有者,则实现共享所有权,例如使用智能指针实现引用计数,如boost::shared_ptr,

但在对象本身上有一个成员函数,这是硬编码的从一个特定列表中删除对象,无论它是否存在于该列表中,也无论它是否也存在于任何其他列表中,并且删除对象本身,无论存在哪些对它的引用,是一个坏主意。

The problem with this is that you're really creating an implicit coupling between the object and the World class.

If I try to call Update() outside the World class, what happens? I might end up with the object being deleted, and I don't know why. It seems the responsibilities are badly mixed up. This is going to cause problems the moment you use the Fire class in a new situation you hadn't thought of when you wrote this code. What happens if the object should be deleted from more than one place? Perhaps it should be removed both from the world, the current map, and the player's inventory? Your Update function will remove it from the world, and then delete the object, and the next time the map or the inventory tries to access the object, Bad Things Happen.

In general, I'd say it is very unintuitive for an Update() function to delete the object it is updating. I'd also say it's unintuitive for an object to delete itself.
The object should more likely have some kind of way to fire an event saying that it has finished burning, and anyone interested can now act on that. For example by removing it from the world. For deleting it, think in terms of ownership.

Who owns the object? The world? That means the world alone gets to decide when the object dies. That's fine as long as the world's reference to the object is going to outlast an other references to it.
Do you think the object own itself? What does that even mean? The object should be deleted when the object no longer exists? Doesn't make sense.

But if there is no clearly defined single owner, implement shared ownership, for example using a smart pointer implementing reference counting, such as boost::shared_ptr

But having a member function on the object itself, which is hardcoded to remove the object from one specific list, whether or not it exists there, and whether or not it also exists in any other list, and also delete the object itself regardless of which references to it exist, is a bad idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文