在物体破坏时留下“僵尸”旗帜

时间:2012-11-10 12:11:09

标签: c++

我想显式地销毁一个对象(调用它上面的析构函数及其所有字段),但可能会发生我仍然持有一些指向该对象的指针。因此,我不想释放记忆;相反,我想留下一种标志“我是一个被摧毁的对象”。

我想到了以下方法:

class BaseClass { //all objects in question derive from this class
public:
    BaseClass() : destroyed(false) {}
    virtual ~BaseClass() {}
private:
    bool destroyed;
public:
    bool isDestroyed() { return destroyed; }
    void destroy() {
        this->~BaseClass(); //this will call the virtual destructor of a derivative class
        new(this) BaseClass();
        destroyed=true;
    }
};

当调用destroy时,我基本上会破坏我拥有的任何对象(可能是衍生对象)并在同一个地方创建一个新的“僵尸”。因此,我希望实现:

  • 以前指向此对象的任何其他指针ptr仍然可以调用ptr->isDestroyed()来验证其存在。
  • 我知道如果我不检查僵尸的旗帜并尝试访问属于任何派生对象的字段,可能会发生不好的事情
  • 我知道僵尸对象仍然消耗与被破坏对象一样多的内存(因为它可能是BaseClass的衍生物)
  • 我仍然需要释放被破坏物体的记忆。但是,我希望调用delete仍然正确吗?

问题:

使用上述模式时是否还有其他问题需要考虑?

在zombie对象上调用delete会正确释放前一个(普通)对象占用的整个内存吗?


虽然我很感激您对如何以不同方式进行操作的意见,我可能倾向于按照您的方式进行操作 - 我仍然希望了解上述代码带来的所有风险。

4 个答案:

答案 0 :(得分:5)

  

有人建议使用智能指针。事实上 - 我这样做,但我的推荐是循环的。我可以使用一些成熟的垃圾收集器,但由于我知道自己在哪里(以及何时!)圈链可以被打破,我想自己利用它。

然后你可以显式null-ify(reset如果你使用shared_ptr)其中一个循环智能指针并打破循环。

或者,如果您事先知道周期的位置,那么您还应该使用weak_ptr代替某些shared_ptr提前避免它们第

---编辑---

  

如果弱指针引用的对象仅被标记为“无效”并释放对其所有包含指针的控制(这句话是否清楚?),我会很高兴。

然后weak_ptr::expired会让你开心:)

答案 1 :(得分:5)

你对你的问题有一些讨厌的评论。现在我不认为他们是应得的,尽管可能有更好的方法来做你想要的。我知道你来自哪里,但实际上你使用的是析构函数,就像使用你拒绝写的重置函数一样。实际上你从调用析构函数中得不到什么,因为调用析构函数与实际删除或重置任何东西无关,除非你实际编写代码在析构函数中执行它。

关于您关于展示位置的问题:

您可能已经知道,placement new不会分配任何内存,因此调用它只会在同一个地方创建对象。我明白这正是你想要的,但它不是必需的。由于你没有在对象上调用delete只是destroy,你可以在不初始化类的情况下将destroy设置为true。

总结一下:

  1. 如果您将析构函数用作常规虚函数,则什么也得不到。不要这样做,因为如果两次调用析构函数你会遇到麻烦
  2. 对placement new的调用不会分配内存,只是执行不必要的初始化。您可以将销毁设置为true。
  3. 要执行您想要正确执行的操作并获得析构函数的好处,您应该重载类的new和delete操作符并使用正常的销毁机制。然后,您可以选择不释放内存但将其标记为无效或者可能释放大部分内存,但将指针指向某些标记。

    修改

    在评论之后,我决定总结我看到的所有风险以及其他人指出的风险:

    1. 在多线程环境中访问无效指针:使用您的方法,可以在析构函数运行之后但在设置destroy标记之前访问类(关于您在其中一条注释中的问题 - shared_ptr用于大多数用途线程安全的)
    2. 中继你不完全控制的行为:你的方法依赖于析构函数自动调用未动态分配的其他成员的析构函数的方式:这意味着你仍然必须释放动态分配内存,你没有控制如何实现这一点,你无法控制调用其他析构函数的顺序。
    3. 转换你并不完全控制的行为(第2点):你正在转发编译器实现析构函数部分的方式,这个析构函数调用其他析构函数你无法确定你的代码是否可移植甚至是如何处理它两次。
    4. 可以调用两次析构函数:根据您的实现,这可能会导致内存泄漏或堆损坏,除非您防止两次释放相同的内存。您声称通过调用新的位置来防范这种情况 - 但是在多线程环境中,这不能保证更多的假设所有内存分配都是由默认构造函数完成的 - 这取决于您的具体实现,这可能是也可能不是真。
    5. 你反对每个回答你问题或对其发表评论的人的更好的判断 - 你可能会遇到一些天才,但很可能你只是通过将你的实现限制在一小部分情况下来拍摄自己。会正常工作。就像使用错误的螺丝刀一样,最终会损坏螺丝。以同样的方式使用语言构造它不打算使用可能最终导致错误的程序 - 析构函数旨在从删除和编译器生成的代码中调用以清除堆栈。直接使用它是不明智的。
    6. 我重复我的建议 - 重载删除和新的你想要的

答案 2 :(得分:2)

与其他人一样,我建议您只使用weak_ptr。但你问为什么你的方法不起作用。你的代码遍布全局,有一些优雅实现和关注点分离的问题,但我不会争论这些问题。相反,我只是指出你的代码非常不是线程安全的。

考虑以下两个控制线程的执行顺序:

// Thread One
if ( ! ptr -> isDestroyed() ) {     // Step One
    // ... interruption ...
    ptr -> something();             // Step Three

另一个:

// Thread Two
ptr -> destroy();                   // Step Two

到第3步到来时,指针不再有效。现在可以通过实现lock()或类似方法来解决这个问题,但现在你已经发生了关于不释放锁的缺陷的可能性。每个人都在推荐weak_ptr的原因是,这类问题已经在类的接口及其实现中得到了解决。

还有一个问题。你似乎想要一个可以随意杀死一个物体的设施。这等于要求对象的唯一指针是弱对象,没有强对象指针在手动删除对象时会破坏。 (我会规定这不是一个坏主意,但我必须说我不知道​​为什么它不适合你。)你可以在weak_ptr和{{1}之上建立这个。 }。这些类是通用的,因此如果您想禁止shared_ptr访问shared_ptr,那么您可以为BaseClass编写一个行为不同的特化。隐藏shared_ptr<BaseClass>的一个实例以防止删除,并通过您控制的工厂方法提供此类指针。

在此模型中,shared_ptr<BaseClass>的语义需要引起注意。第一种选择是您是想要同步还是异步操作。同步destroy()将阻塞,直到释放所有外部指针并且不允许发出新指针。 (我假设指针上已经禁用了复制构造函数。)有两种异步destroy()。如果仍存在外部引用,则两者中较简单的失败。在隐藏的destroy()上调用unique()可以轻松实现此功能。更复杂的一个就像异步I / O调用一样,将破坏安排在将来的某个时刻发生,大概一旦所有外部引用都消失了。此函数可能被称为shared_ptr()以反映语义,因为该对象可能会或可能不会在返回时被销毁。

答案 3 :(得分:1)

我会考虑使用一个合适的智能指针模式。访问已删除对象的行为仍未定义,“僵尸”标志无法提供帮助。与已删除的对象实例关联的内存可能会立即被创建的任何其他对象占用,因此访问僵尸标志不是您可以信任的信息。

IMHO安置新操作员

new(this) BaseClass();
在您的destroy()方法中使用的

实际上并没有帮助。取决于该方法的使用方法。而不是删除已删除对象的派生对象或内部析构函数。在后一种情况下,无论如何都会释放内存。

<强>更新

根据你的编辑,使用共享指针/弱指针习语来解决循环引用的出现不是更好。我会认为这些是一个设计缺陷。