Answers:
怎么了
在编写时,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
。
还有一个旧的名称(C ++ 11之前的版本),auto_ptr
但由于它具有奇怪的复制行为,因此现在已弃用。
然后有一些甚至更聪明的示例,例如std::shared_ptr
,它允许多个指针指向同一个对象,并且仅在销毁最后一个指针时才将其清除。
*p += 2
像使用普通指针那样进行操作。如果未通过引用返回,则不会模仿普通指针的行为,这是这里的意图。
逐步说明:
// 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());
没有泄漏的行为相同。
对于第二个示例,您正在复制指向的对象。该值是该对象的内容,而不是实际的指针。与其他情况一样,&object2 != &*(new A())
。
B object2 = *(new B());
我们丢失了指向已分配内存的指针,因此无法释放它。delete &object2;
可能看起来会起作用,但是因为&object2 != &*(new A())
,它并不等效delete (new A())
,因此无效。
在C#和Java中,您可以使用new创建任何类的实例,然后您不必担心以后将其销毁。
C ++还有一个关键字“ new”可以创建对象,但是与Java或C#不同,它不是创建对象的唯一方法。
C ++具有两种创建对象的机制:
通过自动创建,您可以在范围内的环境中创建对象:-在函数中-在类(或结构)的成员中。
在函数中,您可以通过以下方式创建它:
int func()
{
A a;
B b( 1, 2 );
}
在一个类中,通常可以这样创建它:
class A
{
B b;
public:
A();
};
A::A() :
b( 1, 2 )
{
}
在第一种情况下,退出范围块会自动销毁对象。这可以是一个函数或一个函数中的作用域块。
在后一种情况下,对象b与其作为成员的实例A一起被销毁。
当您需要控制对象的生存期,然后需要删除它来销毁它时,将为对象分配新的对象。使用称为RAII的技术,您可以在创建对象时将其删除,方法是将其放入自动对象中,然后等待该自动对象的析构函数生效。
一个这样的对象是shared_ptr,它将仅在共享对象的shared_ptr的所有实例都被破坏时调用“删除程序”逻辑。
通常,虽然您的代码可能有许多对new的调用,但您对delete的调用应受到限制,并且应始终确保这些调用是从置于智能指针中的析构函数或“ deleter”对象进行的。
您的析构函数也不应抛出异常。
如果这样做,则几乎没有内存泄漏。
automatic
和dynamic
。也有static
。
B object2 = *(new B());
这条线是造成泄漏的原因。让我们将其分开。
object2是类型B的变量,存储在地址1中(是的,我在这里选择任意数字)。在右侧,您要求一个新的B或指向类型B的对象的指针。程序很乐意将其提供给您,并将您的新B分配给地址2,并在地址3中创建一个指针。现在,访问地址2中数据的唯一方法是通过地址3中的指针。接下来,使用*
以获取该指针指向的数据(地址2中的数据)。这有效地创建了该数据的副本,并将其分配给在地址1中分配的object2。请记住,它是COPY,而不是原始副本。
现在,这是问题所在:
您从未真正将指针存储在可以使用它的任何地方!分配完成后,指针(您用来访问地址2的地址3中的内存)超出范围,超出了范围!您无法再对其调用call,因此无法清理address2中的内存。剩下的只是地址1中地址2中数据的副本。内存中有两个相同的东西。一个您可以访问,另一个您不能访问(因为您失去了访问它的路径)。这就是为什么这是内存泄漏。
我建议您从C#的背景知识中学到很多有关C ++指针如何工作的知识。它们是高级主题,可能需要一些时间来掌握,但是它们的使用对您来说是无价的。
如果让它变得更容易,则可以将计算机内存视为旅馆,并且程序是在需要时出租房间的客户。
这家酒店的工作方式是您预订房间并在离开时告诉搬运工。
如果您编程房间而没有通知搬运工就离开了,则搬运工会认为该房间仍在使用中,不会让其他人使用它。在这种情况下,会有房间漏水。
如果您的程序分配了内存但没有删除(它只是停止使用它),则计算机会认为该内存仍在使用中,并且将不允许其他任何人使用它。这是内存泄漏。
这不是确切的类比,但可能会有所帮助。
在创建时,object2
您正在创建使用new创建的对象的副本,但同时也丢失了(从未分配的)指针(因此以后无法删除它)。为了避免这种情况,您必须object2
提供参考。
正是这一行立即泄漏:
B object2 = *(new B());
在这里,您要B
在堆上创建一个新对象,然后在堆栈上创建一个副本。在堆上已分配的那个将无法再访问,因此将导致泄漏。
该行不是立即泄漏的:
A *object1 = new A();
会有泄漏,如果你从来没有delete
ð object1
虽然。