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

Cyg*_*sX1 3 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;
    }
};
Run Code Online (Sandbox Code Playgroud)

destroy被召唤时,我基本上会破坏我拥有的任何物体(可能是衍生物)并在同一个地方创建一个新的"僵尸".因此,我希望实现:

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

问题:

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

调用delete僵尸对象会正确释放前一个(普通)对象消耗的整个内存吗?


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

Bra*_*vic 5

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

然后你可以显式null-ify(如果你使用的话重置shared_ptr)一个圆形智能指针并打破循环.

或者,如果您事先知道周期的位置,您也应该提前避免使用它们来weak_ptr代替某些shared_ptrs.

---编辑---

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

然后weak_ptr::expired应该让你开心:)


Seb*_*bot 5

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

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

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

把它们加起来:

  1. 如果你使用析构函数作为常规虚函数,你什么也得不到.不要这样做,因为如果两次调用析构函数你会遇到麻烦
  2. 对placement new的调用不会分配内存,只是执行不必要的初始化.你可以将destroy设置为true.

要执行您想要正确执行的操作并获得析构函数的好处,您应该重载类的new和delete操作符并使用正常的销毁机制.然后,您可以选择不释放内存,但将其标记为无效或可能释放大部分内存,但将指针指向某些标记.

编辑

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

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

我重复我的建议 - 重载删除和新的你想要的