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

How to prevent double delete without smart pointers?

本文关键字:情况下 删除 指针 智能      更新时间:2023-10-16

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

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这样做,我们就会走上正轨。在 C++11 中,这很容易使用 std::unique_ptr .在早期C++标准中,有std::auto_ptr.虽然早期的C++标准没有移动构造函数,但std::auto_ptr的功能类似,即在分配或复制到新std::auto_ptr时从旧std::auto_ptr放弃资源。

我建议您使用 std::auto_ptr .

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

最后,如果要使其无法复制Foo,只需将复制构造函数和赋值运算符声明为私有运算符即可。

我认为

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

为了确保永远不会发生双重delete,你只打电话delete一个怎么样?考虑以下场景:

    分配
  1. 一个内存池(假设足以分配一千个对象)。这应该通过常规new调用在堆上分配,在新PoolAllocator<T>类中的某个地方分配,并在其 desctructor 中delete
  2. 实现池,使其支持返回内存以供T使用的函数void* getMemory<T>(),并推进内部指针。
  3. 确保无论调用什么T,都只能通过在池提供的内存上放置 new 来创建。

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

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

no no no

no. god no

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。LinkedList 在其自身内部创建节点。用户对它们一无所知,而只是插入 T。然后,链接列表将在删除时删除它们。用户只负责删除链接列表,因为他或她创建了它。

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;

如果你坚持你的建筑,不能听从塔兹廷戈的设计规则;那么答案是肯定的:这是正确的方法!

然后为类 Foo 及其构造函数制作一个强大的 API 文档,从而接管对象 Bar 的终身责任。