提问人:Tony Park 提问时间:12/3/2010 最后编辑:Tony Park 更新时间:12/3/2010 访问量:557
在 C++ 中分配时,我们分配的对象是否会被破坏?
When assigning in C++, does the object we assigned over get destructed?
问:
以下代码片段是否泄漏?如果不是,在 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
}
答:
3赞
Matthew Flaschen
12/3/2010
#1
你正在构建三个对象,所有对象都将被破坏。问题在于,默认的 copy-assignment 运算符将执行浅层复制。这意味着指针被复制,这会导致它被多次删除。这会导致未定义的行为。
这就是 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;
}
评论
0赞
The Maniac
12/3/2010
是的,一旦对象离开范围,它们就会被自动销毁。您(Tony)在构造函数和析构函数中所做的工作可确保在使用B类型的对象时不会泄漏内存。现在,如果您创建了指向 B 类型的对象的指针,则必须显式调用 B 的析构函数以避免泄漏。delete bPtr
7赞
Alex Budovski
12/3/2010
#2
默认的复制分配运算符执行成员复制。
因此,就您而言:
{
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.
}
有关详细信息,请参阅有效 C++ 第 2 版中的第 11 项。
评论
1赞
seand
12/3/2010
您需要编写一个正确的深度复制 operator=,或者阻止调用 operator=。(除非您明确需要,否则这是很好的做法)
1赞
Tony Park
12/3/2010
好的,谢谢。我想这就是为什么他们说要定义一个私人赋值运算符的原因,除非你需要赋值......或者,如果我希望它以任何有用的方式工作,我需要定义一个赋值运算符。
4赞
SingleNegationElimination
12/3/2010
#3
是的,这确实会泄漏。编译器会自动提供额外的方法,因为您尚未定义它。它生成的代码等效于:
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
}
0赞
pythonic metaphor
12/3/2010
#4
正如多次指出的那样,你违反了三法则。只是为了补充链接,在堆栈溢出上对此进行了很好的讨论:什么是三法则?
0赞
Tony Park
12/3/2010
#5
只是为了提供一种不同的方法来解决我最初发布的代码的问题,我想我可以将指针保留在类 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;
}
评论
1赞
Tony Delroy
12/3/2010
好主意,但这里有一个很大的区别,因为 B 对象过去(试图)是独立的、独立的对象。他们“拥有”堆上的 int,并且知道只要他们存在,它就存在。使用此答案中的方法,B 将依赖于传递给构造函数的指针所指示的数据的生存期。B 无法保证这些数据的生命周期,程序员必须更仔细地考虑它。例如,如果你在堆上创建一个新的 B 并从你的函数中返回它,那么你的 pI 和 pJ 已被删除,B 对象被垃圾了。
1赞
Tony Delroy
12/3/2010
(顺便说一句,第三种方法是用共享指针替换原始版本中的指针,这将在内部提供一个安全工具,以便编译器生成的 B 和析构函数版本将“正常工作”。operator=()
operator=()
评论