4

以下代码片段是否泄漏?如果没有,在 foobar() 中构造的两个对象在哪里被破坏?

class B
{
   int* mpI;

public:
   B() { mpI = new int; }
   ~B() { delete mpI; }
};

void foobar()
{
   B b;

   b = B();  // causes construction
   b = B();  // causes construction
}
4

5 回答 5

7

默认的复制赋值运算符执行逐个成员的复制。

所以在你的情况下:

{
  B b;      // default construction.
  b = B();  // temporary is default-contructed, allocating again
            // copy-assignment copies b.mpI = temp.mpI
            // b's original pointer is lost, memory is leaked.
            // temporary is destroyed, calling dtor on temp, which also frees
            // b's pointer, since they both pointed to the same place.

  // b now has an invalid pointer.

  b = B();  // same process as above

  // at end of scope, b's dtor is called on a deleted pointer, chaos ensues.
}

有关详细信息,请参阅 Effective C++,第 2 版中的第 11 项。

于 2010-12-03T00:53:53.257 回答
4

是的,这确实泄漏。编译器会自动提供一个额外的方法,因为您还没有定义它。它生成的代码等价于:

B & B::operator=(const B & other)
{
    mpI = other.mpI;
    return *this;
}

这意味着会发生以下情况:

B b; // b.mpI = heap_object_1

B temp1; // temporary object, temp1.mpI = heap_object_2

b = temp1; // b.mpI = temp1.mpI = heap_object_2;  heap_object_1 is leaked;

~temp1(); // delete heap_object_2; b.mpI = temp1.mpI = invalid heap pointer!

B temp2; // temporary object, temp1.mpI = heap_object_3

b = temp1; // b.mpI = temp2.mpI = heap_object_3; 

~temp1(); // delete heap_object_3; b.mpI = temp2.mpI = invalid heap pointer!

~b(); // delete b.mpI; but b.mpI is invalid, UNDEFINED BEHAVIOR!

这显然很糟糕。这很可能发生在您违反三规则的任何情况下。您已经定义了一个重要的析构函数和一个复制构造函数。但是,您尚未定义复制分配。三法则是,如果你定义了以上任何一个,你应该总是定义所有三个。

相反,请执行以下操作:

class B
{
   int* mpI;

public:
   B() { mpI = new int; }
   B(const B & other){ mpI = new int; *mpi = *(other.mpI); }
   ~B() { delete mpI; }
   B & operator=(const B & other) { *mpI = *(other.mpI); return *this; }
};

void foobar()
{
   B b;

   b = B();  // causes construction
   b = B();  // causes construction
}
于 2010-12-03T01:04:13.970 回答
3

您正在构建三个对象,所有对象都将被破坏。问题是默认的复制赋值操作符会做一个浅拷贝。这意味着指针被复制,这导致它被多次删除。这会导致未定义的行为。

这就是规则 3背后的原因。你有一个析构函数,但没有其他两个。您需要实现一个复制构造函数和复制赋值运算符,它们都应该进行深度复制。这意味着分配一个新的 int,将值复制过来。

B(const B& other) : mpI(new int(*other.mpI)) {
}

B& operator = (const B &other) {
    if (this != &other)
    {
        int *temp = new int(*other.mpI);
        delete mpI;
        mpI = temp;
    }
    return *this;
}
于 2010-12-03T00:53:25.133 回答
0

正如多次指出的那样,您违反了三原则。只是添加到链接中,关于堆栈溢出有一个很好的讨论:什么是三法则?

于 2010-12-03T01:14:43.737 回答
0

只是为了提供一种不同的方法来解决我最初发布的代码的问题,我想我可以将指针保留在 B 类中,但取出内存管理。然后我不需要自定义析构函数,所以我不违反规则 3...

class B
{
   int* mpI;

public:
   B() {}
   B(int* p) { mpI = p; }
   ~B() {}
};

void foobar()
{
   int* pI = new int;
   int* pJ = new int;

   B b;        // causes construction

   b = B(pI);  // causes construction
   b = B(pJ);  // causes construction

   delete pI;
   delete pJ;
}
于 2010-12-03T01:21:10.053 回答