这个C++代码是否会泄漏内存?

new*_*mer 19 c++ memory-leaks assignment-operator

struct Foo
{
    Foo(int i)
    {
        ptr = new int(i);
    }
    ~Foo()
    {
        delete ptr;
    }
    int* ptr;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}
Run Code Online (Sandbox Code Playgroud)

如果我理解正确,编译器将自动为其创建赋值运算符成员函数Foo.但是,这只需要输入ptrin 的值b并将其放入a.a最初分配的内存似乎丢失了.我可以a.~Foo();在进行赋值之前进行调用,但我听说你应该很少需要显式调用析构函数.所以让我们说我编写一个赋值运算符Foo,删除int左操作数的指针,然后将r值赋给l值.像这样:

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        this->ptr = other.ptr;
    }
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我这样做,那么当Foo aFoo b走出去的范围,不要将二者析构函数运行,删除同一指针两次(因为它们都指向同一件事现在)?

编辑:

如果我正确理解Anders K,这是正确的方法:

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        //Clones the int
        this->ptr = new int(*other.ptr);
    }
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

现在,a克隆指向的int那个b,并设置自己的指针.也许在这种情况下,deletenew没有必要,因为它只是涉及intS,但如果数据成员不是一个int*,而是一个Bar*或诸如此类的东西,重新分配可能是必要的.

编辑2: 最佳解决方案似乎是复制和交换习语.

Alo*_*ave 12

这是否会泄漏内存?
不,不.

似乎大多数人都忽略了这一点.所以这里有一点澄清.

在这个答案中"No it not leak"的初始响应是不正确的,但是这里提出的解决方案解决该问题的唯一且最合适的解决方案.


你的困境的解决方案是:

不使用指向整数成员(int *)的指针,而只使用整数(int),这里不需要动态分配指针成员.您可以使用intas成员实现相同的功能.
请注意,在C++中,您应该new尽可能少地使用.

如果由于某种原因(我在代码示例中看不到)你不能没有动态分配的指针成员读取:

你需要遵循三法则!


为什么你需要遵守三法则?

三条规则规定:

如果你的班级需要

一个拷贝构造函数,
一个赋值运算符,
析构函数,

然后它可能需要所有这三个.

您的类需要一个自己的显式析构函数,因此它还需要一个显式的复制构造函数和复制赋值运算符.
由于您的类的复制构造函数和复制赋值运算符是隐式的,因此它们也是隐式公共的,这意味着类设计允许复制或分配此类的对象.隐式生成的这些函数版本只会生成动态分配的指针成员的浅表副本,这会将您的类暴露给:

  • 内存泄漏&
  • 晃来晃去的指针
  • 双重释放的潜在未定义行为

这基本上意味着你不能使用隐式生成的版本,你需要提供自己的重载版本,这就是三个规则所说的开头.

明确提供的重载应该对已分配的成员进行深层复制,从而防止所有问题.

如何正确实现复制赋值运算符?

在这种情况下,提供复制赋值运算符的最有效和最优化的方法是使用:
copy-and-swap Idiom
@ GManNickG的着名答案提供了足够的细节来解释它提供的优势.


建议:

另外,使用智能指针作为类成员而不是使用显式内存管理负担的原始指针会更好.智能指针将隐式管理内存.要使用哪种智能指针取决于您的成员的生命周期所有权语义,您需要根据您的要求选择合适的智能指针.

  • 这是错的.链接的示例确实泄漏了内存(您可以像其他人建议的那样使用valgrind进行检查)并在剩余的引导指针上进行双重释放. (5认同)
  • @AndyRoss:好的,是的,但它仍然没有改变答案所暗示的解决方案.答案是错误的(疏忽)说*没有记忆泄漏*,但它比任何答案都更正确.我没注意它泄漏记忆的事实,因为我注意到的第一件事是程序不遵循**三法则**并从那里开始几乎打开了一堆蠕虫. (2认同)

And*_*rsK 10

处理此问题的常用方法是创建指针指向的对象的克隆,这就是为什么拥有赋值运算符很重要的原因.当没有定义分配运算符时,默认行为是一个memcpy,当两个析构函数试图删除同一个对象并且内存泄漏时会导致崩溃,因为前一个值ptr指向b中的值不会被删除.

Foo a

         +-----+
a->ptr-> |     |
         +-----+

Foo b

         +-----+
b->ptr-> |     |
         +-----+

a = b

         +-----+
         |     |
         +-----+
a->ptr            
       \ +-----+
b->ptr   |     |
         +-----+

when a and b go out of scope delete will be called twice on the same object.
Run Code Online (Sandbox Code Playgroud)

编辑:正如本杰明/阿尔斯正确指出的那样,上面只是指这个特例,见下面的评论

  • *"当没有定义分配运算符时,默认行为是memcpy"* - 否.默认行为是递归调用所有子对象的赋值运算符.在POD的情况下,这相当于memcpy,是的.也许你不是一般性的说话而只是提到这个具体的案例,但我认为你的措辞并不清楚. (12认同)