对象是否应该在C ++中删除自己?

时间:2009-02-06 23:17:49

标签: c++ oop memory-management resource-cleanup self-destruction

我在C#度过了最近4年,所以我对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;
        }
    }
}

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

这是一件明智的事情,还是有更好的设计可以帮助清理这些物品?

11 个答案:

答案 0 :(得分:30)

您已将Update设为虚函数,建议派生类可以覆盖Update的实现。这带来了两大风险。

1。)被覆盖的实现可能记得执行World.Remove,但可能会忘记delete this。记忆泄露了。

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

考虑这个例子:

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

答案 1 :(得分:20)

这个问题是你真的在对象和World类之间创建一个隐式耦合。

如果我尝试在World类之外调用Update(),会发生什么?我可能最终将对象删除,我不知道为什么。似乎责任严重混乱。当您在编写此代码时没有想到的新情况下使用Fire类时,这将导致问题。如果要从多个地方删除对象会怎样?也许它应该从世界,当前地图和玩家的库存中删除?您的更新功能将从世界中删除它,然后删除该对象,并在下次地图或库存尝试访问该对象时,Bad Things Happen。

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

谁拥有这个对象?世界?这意味着只有世界才能决定对象何时死亡。只要世界对该对象的引用比其他对它的引用更持久,那就没问题。 你觉得这个物体属于自己吗?那有什么意思?当对象不再存在时,应删除该对象?没有意义。

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

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

答案 2 :(得分:6)

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

答案 3 :(得分:6)

如果对象分配并使用new构造,则删除将失败并可能导致程序崩溃。此构造将阻止您在堆栈上或静态地实例化类。在您的情况下,您似乎正在使用某种分配方案。如果坚持这一点,这可能是安全的。不过,我当然不会这样做。

答案 4 :(得分:5)

我更喜欢更严格的所有权模式。世界应该拥有其中的物品并负责清理它们。要么有世界删除,要么更新(或其他功能)返回一个值,表明应该删除一个对象。

我也猜测在这种类型的模式中,你会想要引用计数你的对象以避免以悬空指针结束。

答案 5 :(得分:5)

它肯定适用于new创建的对象,以及Update的调用者正确了解该行为的时间。但我会避免它。在你的情况下,所有权显然是在世界,所以我会让世界删除它。该对象不会创建自己,我认为它也不应该删除自己。如果你在你的对象上调用一个函数“Update”可能会非常令人惊讶,但是如果没有World自己做任何事情(除了将它从列表中删除 - 但在另一个框架中,那么突然该对象不再存在!代码调用对象的更新不会注意到这一点。

关于此的一些想法

  1. 向World添加对象引用列表。该列表中的每个对象都有待删除。这是一种常见的技术,并且在wxWidgets中用于处理已关闭但仍可能接收消息的窗口。在空闲时,处理所有消息时,将处理挂起列表,并删除对象。我相信Qt遵循类似的技术。
  2. 告诉全世界想要删除该对象。世界将获得适当的信息,并将负责任何需要完成的工作。可能像deleteObject(Object&)那样。
  3. 向每个对象添加shouldBeDeleted函数,如果对象希望被其所有者删除,则返回true。
  4. 我更喜欢选项3.世界会称之为更新。之后,它会查看对象是否应该被删除,并且可以这样做 - 或者如果它愿意,它会通过手动将该对象添加到挂起删除列表来记住这一事实。

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

    引用计数对象的自删除有气味,imho。让我们比较一下:

    // 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();
    
    };
    

    将其与自删除的refcounted对象进行比较:

    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”如何成为~Ditmap的析构函数中的悬空指针。可以说,不这样做“删除这个”在这里要好得多。

    回答类似的问题"What is the use of delete this"

答案 6 :(得分:2)

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

然而,我也看到它崩溃了。我希望我能记住坠机的情况。 @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;
};

答案 7 :(得分:2)

其他人提到了“删除这个”的问题。简而言之,既然“世界”管理着Fire的创建,它也应该管理它的删除。

另一个问题是,如果世界因火还在燃烧而结束。如果这是唯一可以摧毁火灾的机制,那么你最终可能会遇到孤儿或垃圾火灾。

对于你想要的语义,我会在你的“Fire”类(或者对象,如果适用)中有一个“active”或“alive”标志。那么世界有时会检查它的物品库存,并摆脱那些不再活跃的物品。

-

还有一点需要注意的是,您编写的代码具有Fire私有继承自Object的权限,因为这是默认的,即使它比公共的不太常见。你可能应该明确表示这种继承,我怀疑你真的想要公开继承。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

删除这是非常危险的。这没什么“错”的。这是合法的。但是当你使用它时会有很多东西可以出错,应该谨慎使用它。

考虑某人有开放指针或对象引用的情况,然后它会自行删除。

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