如何在没有智能指针的情况下防止双重删除?

How to prevent double delete without smart pointers?

也许这是一个愚蠢的问题,但我只是想确保不要搞砸了。假设我有这个结构:

struct Foo{
    struct Bar { virtual int calc(int x) = 0; };
    Bar* barX;
    Bar* barY;
    int x,y;
    Foo(Bar* bx,Bar* by) : barX(by),barY(by) {}
    void process(int xx,int yy){
        x = barX->calc(xx);
        y = barY->calc(yy);
    }
    ~Foo();
};

struct IDBar : Foo::Bar { int calc(int x) { return x; } };

int main() {
    IDBar* b = new IDBar();
    Foo f = Foo(b,b);
    Foo f2 = Foo(new IDBar(),new IDBar());
}

我不能使用 C++11(即没有智能指针),而且我不是 100% 确定...这是删除两个(或可能只有一个)Bar 个对象:

Foo::~Foo(){
    if (barX == barY){ delete barX; }
    else { delete barX; delete barY; }
}

?

PS:想法是 Foo 拥有 Bar 对象(因此将负责删除它们)。传递给构造函数的 Bar 不应用于其他任何用途。实际上一个 Bar 只能属于一个 Foo (我后来才意识到的一个缺陷,但现在还好)。此外, Foo 不应该被复制(也许我应该明确地阻止它)。

不不不不。上帝不

Foo::~Foo(){
        if (barX == barY){ delete barX; }
        else { delete barX; delete barY; }
    }

删除对象不是自己分配的内存是非常危险的。拥有这些对象的任何人都应该删除它们。在你的例子中,main 应该负责删除那些分配的对象。

int main() {
    IDBar* b = new IDBar();
    IDBar* b2 = new IDBar();
    IDBar* b3 = new IDBar();
    Foo f = Foo(b,b);
    Foo f2 = Foo(b2,b3);
    delete b;
    delete b2;
    delete b3;
}

一个处理内存管理的对象本身的完美例子是链表。 LinkedList 在其自身内部创建节点。用户不知道它们,而只是插入 T。LinkedList 将在删除时删除它们。用户只负责删除 LinkedList,因为他或她创建了它。

std::list<MyObject*> list;
MyObject* object = new MyObject;
list.push_back(object);
//The list isn't going to delete object because it doesn't own it
delete object;

如果你坚持自己的施工,不能遵守Taztingo的设计规则;那么答案是肯定的:这是正确的方法!

然后为 class Foo 及其构造函数创建一个强大的 API 文档,接管对象 Bar 的生命周期责任。

我认为比较远非完美 - 想象一下你有 三个 指针。

为了确保不会出现双重 delete,您只调用一个 delete 怎么样?考虑场景:

  1. 分配一个内存池(假设足以分配一千个对象)。这应该通过常规 new 调用在堆上分配,位于新的 PoolAllocator<T> class 和 delete 其 desctructor 中的某处。
  2. 实施池,使其支持功能 void* getMemory<T>() 返回内存供 T 使用,并推进内部指针。
  3. 确保调用 T 的任何内容都只能通过在提供的内存池上放置 new 来创建。

最后,您不必担心内存 - 池对象可以是一个常规的堆栈变量,在堆上有内部结构。

否则,是什么禁止您实现自己的原型智能指针?

我对 Taztingo 有一点不同意见。对象获得传递给它的资源的所有权并不是不合理的。这发生在各种现实世界的代码中。 class 获取传递给构造函数的资源的所有权这一事实已记录在案,仅此而已。如果有人在未查阅文档的情况下不当使用 class,那他们就是搬起石头砸自己的脚。

然而,这确实容易出错,我们希望捕获此类错误或防止它们发生。如果有某种方法可以保证在 Foo 拥有资源时没有其他人认为他们拥有资源,我们就会继续努力。在 C++11 中,使用 std::unique_ptr 很容易。在早期的 C++ 标准中,有 std::auto_ptr。虽然早期的 C++ 标准没有移动构造函数,但 std::auto_ptr 通过在分配或复制到新的 std::auto_ptr.

时放弃旧 std::auto_ptr 的资源来实现类似的功能

我推荐你使用std::auto_ptr

如果明确允许为 class Foo 的两个构造函数参数传入相同资源的情况,请创建一个仅接受一个 std::auto_ptr<Bar> 的新构造函数。

最后,如果你想让Foo不能被复制,只需将复制构造函数和赋值运算符都声明为私有即可。