我无法帮助阅读关于析构函数的大量论坛帖子并完全混淆。
有人说要将析构函数(delete
)once for each call调用new
。有人说当对象超出范围时,析构函数会在各种情况下自动调用,即when the pointer is reassigned。有些人建议指针超出范围,同时作为返回值,其中对象作为其前身的副本存在,(这是否需要显式销毁,因为它最初是使用new
创建的?
似乎some suggestion多次调用相同的析构函数会破坏内存,因此所有delete
调用都应该与*pointer = NULL;
合作以避免损坏。如果没有,那么一些更高级的对象管理系统将需要实施,或者铁腕的严格所有权。
我似乎无法对调用析构函数序列进行任何讨论,即调用1)是否源自基类超类并级联到特定类,在途中调用所有虚拟化析构函数,2)发起在实例化的类中,向上移动到超类,或者3)在类超出范围时从该特定的转换开始,然后遍历实例化和基类。做级联析构函数
最终,我根本不知道如何或何时删除对象(如果有的话),对象是否负责删除它们引用的所有对象,如何干净地处理适当的面向对象的删除例程,其中多次引用对象,这只是我脑子里的一团糟。正如你所看到的,我无法真正提出一个可靠的问题,我真的希望有人可以提供一个简洁而简洁的讨论,如果不是单一的“正确”方法,至少是反对删除的行业最佳实践。
答案 0 :(得分:8)
有三种类型的分配,以不同的方式调用析构函数:
这些对象驻留在自动内存中(通常是堆栈):
int main()
{
A a;
//...
}
当a
超出范围(关闭a
)时,会自动调用}
的析构函数。
对象驻留在动态内存(堆)中。它们被分配new
并且为了调用dstructor,您需要调用delete
:
int main()
{
A* a = new A;
delete a; //destructor called
}
在这种情况下,可能建议您在NULL
之后将a
分配给delete
。关于这个有两种思想流派(我个人不会建议)。我的动机是您可以在delete
上再次致电a
,如果您未将其设置为NULL
,则会导致该程序崩溃。哪个是对的。但是如果再次调用delete
,逻辑中已经存在错误或错误,不应该通过使代码看起来正确运行来掩盖错误。
对象驻留在static
内存中。无论它们在何处分配,程序结束时都会自动调用析构函数:
A a; //namespace scope
int main()
{
}
这里,在A
完成后程序终止时调用main
析构函数。
答案 1 :(得分:3)
C ++语言让程序员掌握了内存管理,这就是你可以找到这种混乱程度的原因。
重复Luchian Grigore说有三种主要类型的记忆
如果在自动存储中分配对象,则一旦范围终止,该对象将被销毁;例如
void foo() {
MyClass myclass_instance;
myclass_instance.doSomething();
}
在上述情况下,当函数终止myclass_instance
时会自动销毁。
如果您使用new
在堆中分配对象,那么您有责任使用delete
调用析构函数。
在C ++中,对象也可以有子对象。例如:
class MyBiggerClass {
MyClass x1;
MyClass x2;
...
};
这些子对象分配在同一内存中,包含对象被分配给
void foo() {
MyBiggerClass big_instance;
MyBiggerClass *p = new MyBiggerClass();
...
delete p;
}
在上面的例子中,两个子对象big_instance.x1
和big_instance.x2
将在自动存储(堆栈)中分配,而p->x1
和p->x2
在堆上分配
但是请注意,在这种情况下,您不需要调用delete p->x1;
(编译错误,p->x1
不是指针)或delete &(p->x1);
(语法上有效,但逻辑错误,因为它没有在堆上显式分配,而是作为另一个对象的子对象)。只需删除主对象p
即可。
另一个复杂因素是对象可能会保留指向其他对象的指针,而不是直接包含它们:
class MyOtherBigClass {
MyClass *px1;
MyClass *px2;
};
在这种情况下,MyOtherBigClass
的构造函数必须找到子对象的内存,并且~MyOtherBigClass
将需要处理销毁子对象并释放记忆。
在C ++中,销毁原始指针不会自动销毁内容。
简单情况下的基类可以看作是隐藏的嵌入式子对象。即就像基础对象的实例嵌入在派生对象中一样。
class MyBaseClass {
...
};
class MyDerivedClass : MyBaseClass {
MyBaseClass __base__; // <== just for explanation of how it works: the base
// sub-object is already present, you don't
// need to declare it and it's a sub-object that
// has no name. In the C++ standard you can find
// this hidden sub-object referenced quite often.
...
};
这意味着派生对象的析构函数不需要调用基础对象的析构函数,因为语言会自动处理它。 虚拟基础的情况更复杂,但仍然会自动调用基础析构函数。
鉴于内存管理是程序员的控制,有一些策略可以帮助程序员避免乱七八糟的复杂代码,这些代码总是会导致对象泄漏或多次破坏。
仔细计划如何处理实例的生命周期。你不能只是将其作为事后的想法,因为以后无法修复。对于每个对象实例,应该清楚谁创建和破坏它。
如果无法提前计划销毁对象,则使用引用计数器:对于每个对象,跟踪引用它的指针数量,并在此数字达到零时销毁对象。 智能指针可以为您解决此问题。
永远不要留下指向已经被破坏的物体的指针。
使用明确设计的类容器来处理包含对象的生命周期。示例包括std::vector
或std::map
。
答案 2 :(得分:0)
如果您的代码调用了new
,那么它也应该调用delete
,是的。除非您使用智能指针(当指针被销毁时将为您调用delete
)。只要有可能,您应该使用智能指针并使用vector
或string
来避免使用new
手动分配内存 - 如果您不拨打new
,则不要需要担心确保调用delete
- &gt;没有内存泄漏,也没有在错误的时间销毁对象等问题。
对同一个实例多次调用delete
绝对不是一个好主意。
如果我们有这个:
class A
{
int *p;
public:
A() { p = new int[10]; }
~A() { delete [] p; }
};
class B
{
A a;
~B() { ... }
...
};
class C : public B
{
...
~C() { ... }
}
...
C *cp = new C;
....
delete cp;
然后通过delete调用C
的析构函数。 B
的析构函数由C
析构函数调用,A
的析构函数由B
析构函数调用。这是自动的,编译器将“确保发生这种情况”。
如果我们不打电话给新人:
...
{
C c;
...
} // Destructor for C gets called here (and B and A as describe above)