为什么使用'new'导致内存泄漏?
我首先学习了C#,现在我开始使用C ++。 据我所知,C ++中的new
操作符与C#中的操作符不同。
你能解释这个示例代码中内存泄漏的原因吗?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
发生什么事
当你写T t;
你正在创建一个T
类型的对象,并自动存储持续时间。 当它超出范围时它会自动清理。
当你写new T()
你正在创建一个具有动态存储持续时间的T
型对象。 它不会自动清理。
你需要传递一个指针来delete
它以清理它:
然而,你的第二个例子更糟糕:你正在取消引用指针,并复制该对象。 这样你就失去了用new
创建的对象的指针,所以即使你想要,也不能删除它!
你应该做什么
您应该更喜欢自动存储时间。 需要一个新的对象,只需写:
A a; // a new object of type A
B b; // a new object of type B
如果确实需要动态存储持续时间,请将指针存储到自动存储持续时间对象中,该对象会自动删除该对象。
template <typename T>
class automatic_pointer {
public:
automatic_pointer(T* pointer) : pointer(pointer) {}
// destructor: gets called upon cleanup
// in this case, we want to use delete
~automatic_pointer() { delete pointer; }
// emulate pointers!
// with this we can write *p
T& operator*() const { return *pointer; }
// and with this we can write p->f()
T* operator->() const { return pointer; }
private:
T* pointer;
// for this example, I'll just forbid copies
// a smarter class could deal with this some other way
automatic_pointer(automatic_pointer const&);
automatic_pointer& operator=(automatic_pointer const&);
};
automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically
这是一个不常用的描述性名称RAII(资源获取初始化)的常见成语。 当您获取需要清理的资源时,可以将其保存在自动存储期限的对象中,因此您无需担心清理它。 这适用于任何资源,无论是内存,打开的文件,网络连接,或任何你喜欢的。
这个automatic_pointer
事物已经以各种形式存在,我刚刚提供了一个例子。 标准库中存在一个非常类似的类,名为std::unique_ptr
。
还有一个名为auto_ptr
的旧版本(pre-C ++ 11),但现在已弃用,因为它有一个奇怪的复制行为。
然后有一些更聪明的例子,比如std::shared_ptr
,它允许多个指针指向同一个对象,并且只在最后一个指针被销毁时清理它。
一步一步的解释:
// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());
所以最后,你在堆上有一个没有指针的对象,所以不可能删除。
另一个样本:
A *object1 = new A();
仅当您忘记delete
分配的内存时才会发生内存泄漏:
delete object1;
在C ++中,存在自动存储的对象,堆栈中创建的对象以及具有动态存储的对象,这些对象将分配给new
的堆,并且需要使用delete
来释放自己。 (这大致都是)
认为你应该delete
每个分配了new
对象。
编辑
来想一想, object2
不一定是内存泄漏。
下面的代码只是为了说明一点,这是一个坏主意,从来不喜欢这样的代码:
class B
{
public:
B() {}; //default constructor
B(const B& other) //copy constructor, this will be called
//on the line B object2 = *(new B())
{
delete &other;
}
}
在这种情况下,由于other
通过引用传递,它将成为new B()
指向的确切对象。 因此,通过&other
获得其地址并删除指针将释放内存。
但我无法强调这一点,不要这样做。 这仅仅是为了说明问题。
给定两个“对象”:
obj a;
obj b;
他们不会在记忆中占据相同的位置。 换句话说, &a != &b
将一个值赋给另一个不会改变它们的位置,但是它会改变它们的内容:
obj a;
obj b = a;
//a == b, but &a != &b
直观上,指针“对象”的工作方式相同:
obj *a;
obj *b = a;
//a == b, but &a != &b
现在,让我们看看你的例子:
A *object1 = new A();
这是将new A()
的值object1
。 该值是一个指针,意思是object1 == new A()
,但是&object1 != &(new A())
。 (请注意,这个例子不是有效的代码,它仅用于解释)
由于指针的值被保留,我们可以释放它指向的内存: delete object1;
由于我们的规则,这与delete (new A());
行为相同delete (new A());
没有泄漏。
对于第二个示例,您正在复制指向的对象。 该值是该对象的内容,而不是实际的指针。 和其他情况一样, &object2 != &*(new A())
。
B object2 = *(new B());
我们失去了指向分配内存的指针,因此我们无法释放它。 delete &object2;
看起来可能会起作用,但是因为&object2 != &*(new A())
,它不等于delete (new A())
,因此无效。