以下代码片段是否泄漏?如果没有,在 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
}
以下代码片段是否泄漏?如果没有,在 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
}
默认的复制赋值运算符执行逐个成员的复制。
所以在你的情况下:
{
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 项。
是的,这确实泄漏。编译器会自动提供一个额外的方法,因为您还没有定义它。它生成的代码等价于:
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
}
您正在构建三个对象,所有对象都将被破坏。问题是默认的复制赋值操作符会做一个浅拷贝。这意味着指针被复制,这导致它被多次删除。这会导致未定义的行为。
这就是规则 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;
}
正如多次指出的那样,您违反了三原则。只是添加到链接中,关于堆栈溢出有一个很好的讨论:什么是三法则?
只是为了提供一种不同的方法来解决我最初发布的代码的问题,我想我可以将指针保留在 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;
}