简体   繁体   English

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

[英]Does this C++ code leak memory?

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
}

If I understand correctly, the compiler will automatically create an assignment operator member function for Foo . 如果我理解正确,编译器将自动为Foo创建赋值运算符成员函数。 However, that just takes the value of ptr in b and puts it in a . 但是, ptrbptr值并将其放入a The memory allocated by a originally seems lost. 根据所分配的内存a原本似乎失去。 I could do a call a.~Foo(); 我可以打电话a.~Foo(); before making the assignment, but I heard somewhere that you should rarely need to explicitly call a destructor. 在进行赋值之前,我听说你应该很少需要显式地调用析构函数。 So let's say instead I write an assignment operator for Foo that deletes the int pointer of the left operand before assigning the r-value to the l-value. 所以我想说我为Foo编写了一个赋值运算符,它在将r值赋给l值之前删除了左操作数的int指针。 Like so: 像这样:

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

But if I do that, then when Foo a and Foo b go out of scope, don't both their destructors run, deleting the same pointer twice (since they both point to the same thing now)? 但是如果我这样做,那么当Foo aFoo b超出范围时,不要同时运行它们的析构函数,删除相同的指针两次(因为它们现在都指向同一个东西)?

Edit: 编辑:

If I understand Anders K correctly, this is the proper way to do it: 如果我正确理解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;
}

Now, a cloned the int that b pointed to, and sets its own pointer to it. 现在, a克隆的intb指出,并设置自己的指针。 Perhaps in this situation, the delete and new were not necessary because it just involves int s, but if the data member was not an int* but rather a Bar* or whatnot, a reallocation could be necessary. 也许在这种情况下, deletenew不是必需的,因为它只涉及int ,但如果数据成员不是int*而是Bar*或诸如此类的东西,则可能需要重新分配。

Edit 2: The best solution appears to be the copy-and-swap idiom . 编辑2:最佳解决方案似乎是复制和交换习语

Whether this leaks memory? 这是否会泄漏内存?
No it doesn't. 不,不。

It seems most of the people have missed the point here. 似乎大多数人都忽略了这一点。 So here is a bit of clarification. 所以这里有一点澄清。

The initial response of "No it doesn't leak" in this answer was Incorrect but the solution that was and is suggested here is the only and the most appropriate solution to the problem. 在这个答案中“No it not leak”的初始响应是不正确的,但是这里提出的解决方案解决该问题的唯一且最合适的解决方案。


The solution to your woes is: 你的困境的解决方案是:

Not use a pointer to integer member( int * ) but to use just an integer ( int ), You don't really need dynamically allocated pointer member here. 不使用指向整数成员( int * )的指针,而只使用整数( int ),这里不需要动态分配指针成员。 You can achieve the same functionality using an int as member. 您可以使用int作为成员来实现相同的功能。
Note that in C++ You should use new as little as possible. 请注意,在C ++中, 您应该尽可能少地使用new

If for some reason(which I can't see in the code sample) You can't do without dynamically allocated pointer member read on: 如果由于某种原因(我在代码示例中看不到)你不能没有动态分配的指针成员读取:

You need to follow the Rule of Three! 你需要遵循三法则!


Why do you need to follow Rule of Three? 为什么你需要遵守三法则?

The Rule of Three states: 三国统治

If your class needs either 如果你的班级需要

a copy constructor , 复制构造函数
an assignment operator , 赋值运算符
or a destructor , 或者是一个析构函数

then it is likely to need all three of them . 然后它可能需要所有这三个

Your class needs an explicit destructor of its own so it also needs an explicit copy constructor and copy assignment operator. 您的类需要一个自己的显式析构函数,因此它还需要一个显式的复制构造函数和复制赋值运算符。
Since copy constructor and copy assignment operator for your class are implicit , they are implicitly public as well, Which means the class design allows to copy or assign objects of this class. 由于您的类的复制构造函数和复制赋值运算符是隐式的 ,因此它们也是隐式公共的 ,这意味着类设计允许复制或分配此类的对象。 The implicitly generated versions of these functions will only make a shallow copy of the dynamically allocated pointer member, this exposes your class to: 隐式生成的这些函数版本只会生成动态分配的指针成员的浅表副本 ,这会将您的类暴露给:

  • Memory Leaks & 内存泄漏&
  • Dangling pointers & 晃来晃去的指针
  • Potential Undefined Behavior of double deallocation 双重释放的潜在未定义行为

Which basically means you cannot make do with the implicitly generated versions, You need to provide your own overloaded versions and this is what Rule of Three says to begin with. 这基本上意味着你不能使用隐式生成的版本,你需要提供自己的重载版本,这就是三个规则所说的开头。

The explicitly provided overloads should make a deep copy of the allocated member and it thus prevents all your problems. 显式提供的重载应该是已分配成员的深层副本 ,因此可以防止所有问题。

How to implement the Copy assignment operator correctly? 如何正确实现复制赋值运算符?

In this case the most efficient and optimized way of providing a copy assignment operator is by using: 在这种情况下,提供复制赋值运算符的最有效和最优化的方法是使用:
copy-and-swap Idiom 复制交换习语
@GManNickG's famous answer provides enough detail to explain the advantages it provides. @ GManNickG的着名答案提供了足够的细节来解释它提供的优势。


Suggestion: 建议:

Also, You are much better off using smart pointer as an class member rather than a raw pointer which burdens you with explicit memory management. 此外,使用智能指针作为类成员而不是使用显式内存管理负担的原始指针会更好。 A smart pointer will implicitly manage the memory for you. 智能指针将隐式管理内存。 What kind of smart pointer to use depends on lifetime and ownership semantics intended for your member and you need to choose an appropriate smart pointer as per your requirement. 要使用哪种智能指针取决于您的成员的生命周期所有权语义,您需要根据您的要求选择合适的智能指针

the normal way to handle this is to create a clone of the object the pointer points to, that is why it is important to have an assignment operator. 处理此问题的常用方法是创建指针指向的对象的克隆,这就是为什么拥有赋值运算符很重要的原因。 when there is no assigment operator defined the default behavior is a memcpy which will cause a crash when both destructors try to delete the same object and a memory leak since the previous value ptr was pointing to in b will not be deleted. 当没有定义分配运算符时,默认行为是一个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.

edit: as Benjamin/Als correctly pointed out the above is just referring to this particular example, see below in comments 编辑:正如本杰明/阿尔斯正确指出的那样,上面只是指这个特例,见下面的评论

The code as presented has Undefined Behavior. 显示的代码具有未定义的行为。 As such, if it leaks memory (as expected) then that is just one possible manifestation of the UB. 因此,如果它泄漏内存(如预期的那样)那么这只是UB的一种可能表现形式。 It can also send an angry threatening letter to Barack Obama, or spew out red (or orange) nasal daemons, or do nothing, or act as if there was no memory leak, miraculously reclaiming the memory, or whatever. 它还可以向巴拉克奥巴马发送愤怒的威胁信,或者喷出红色(或橙色)鼻涕守护进程,或者什么也不做,或者好像没有记忆泄漏,奇迹般地回收记忆,或其他什么。

Solution: instead of int* , use int , ie 解决方案:使用int ,而不是int*

struct Foo
{
    Foo(int i): blah( i ) {}
    int blah;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}

That's safer, shorter, far more efficient and far more clear. 这更安全,更短,更有效,更清晰。

No other solution presented for this question, beats the above on any objective measure. 没有针对这个问题的其他解决方案,在任何客观衡量标准上胜过上述内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM