我的项目中遇到了与内存相关的崩溃。我设法将其减少到以下“玩具”项目:
class Foo {
public:
Foo() : bar(nullptr) {
bar = new int(3);
}
~Foo() {
if (bar) {
delete bar;
bar = nullptr;
}
}
private:
int* bar;
};
Foo test() {
Foo foo;
return foo;
}
int main() {
test();
// <--- Crash!
return 0;
}
我无法弄清楚为什么我会在指定的线路上崩溃。这是我到目前为止收集的内容,如果我错了,请纠正我:
基本上,我在foo
的堆栈上创建test()
。 Foo
在堆上分配一些内存。一切都很好。然后我尝试返回foo
。返回foo
,但会立即销毁。然后,当我退出时,我再次试图摧毁foo
;因此我两次调用Foo
的析构函数,然后崩溃了。我没有得到的是为什么这是一个问题。我在删除它之前检查Foo::bar
是否为null,如果我删除它,我之后将其设置为null。
为什么会导致崩溃?我怎样才能解决这个问题?我究竟做错了什么?我很困惑! :(
更新:发生这种情况的主要原因是缺少复制构造函数,如下面的答案中所述。但是,我的原始项目确实有复制构造函数,或者我认为。如果您的类是派生的,那么您必须为派生类显式创建一个复制构造函数。 Base(const Derived& other)
不算作复制构造函数!
答案 0 :(得分:9)
您违反了rule of three。
当您在构造函数和析构函数中手动管理内存时,您还必须提供复制构造函数和赋值运算符,否则您的程序将在删除后双重删除并使用内存。
在您的特定示例中,您具有编译器提供的复制构造函数。将foo
复制到test
的返回值时,您有两个具有相同bar
指针的对象。本地foo
超出范围并被销毁,然后其bar
设置为nullptr
。但是副本(返回值)仍然具有非空指针。然后它也被销毁,并再次删除相同的内存。
答案 1 :(得分:2)
您需要实现一个复制构造函数,并且很可能是一个赋值运算符。
但是,真的,你的问题在于手动管理内存。这几乎总是一个糟糕的主意,因为它是错误进入代码的最常见方式之一。使用shared_ptr
而不是手动管理代码中的内存,令人惊讶的是代码的维护更加轻松!