c ++ Destructors,何时何地?

时间:2013-08-05 09:22:56

标签: c++ destructor

我无法帮助阅读关于析构函数的大量论坛帖子并完全混淆。

有人说要将析构函数(deleteonce for each call调用new。有人说当对象超出范围时,析构函数会在各种情况下自动调用,即when the pointer is reassigned。有些人建议指针超出范围,同时作为返回值,其中对象作为其前身的副本存在,(这是否需要显式销毁,因为它最初是使用new创建的?

似乎some suggestion多次调用相同的析构函数会破坏内存,因此所有delete调用都应该与*pointer = NULL;合作以避免损坏。如果没有,那么一些更高级的对象管理系统将需要实施,或者铁腕的严格所有权。

我似乎无法对调用析构函数序列进行任何讨论,即调用1)是否源自基类超类并级联到特定类,在途中调用所有虚拟化析构函数,2)发起在实例化的类中,向上移动到超类,或者3)在类超出范围时从该特定的转换开始,然后遍历实例化和基类。做级联析构函数

最终,我根本不知道如何或何时删除对象(如果有的话),对象是否负责删除它们引用的所有对象,如何干净地处理适当的面向对象的删除例程,其中多次引用对象,这只是我脑子里的一团糟。正如你所看到的,我无法真正提出一个可靠的问题,我真的希望有人可以提供一个简洁而简洁的讨论,如果不是单一的“正确”方法,至少是反对删除的行业最佳实践。

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.x1big_instance.x2将在自动存储(堆栈)中分配,而p->x1p->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.
    ...
};

这意味着派生对象的析构函数不需要调用基础对象的析构函数,因为语言会自动处理它。 虚拟基础的情况更复杂,但仍然会自动调用基础析构函数。

鉴于内存管理是程序员的控制,有一些策略可以帮助程序员避免乱七八糟的复杂代码,这些代码总是会导致对象泄漏或多次破坏。

  1. 仔细计划如何处理实例的生命周期。你不能只是将其作为事后的想法,因为以后无法修复。对于每个对象实例,应该清楚谁创建和破坏它。

  2. 如果无法提前计划销毁对象,则使用引用计数器:对于每个对象,跟踪引用它的指针数量,并在此数字达到零时销毁对象。 智能指针可以为您解决此问题。

  3. 永远不要留下指向已经被破坏的物体的指针。

  4. 使用明确设计的类容器来处理包含对象的生命周期。示例包括std::vectorstd::map

答案 2 :(得分:0)

如果您的代码调用了new,那么它也应该调用delete,是的。除非您使用智能指针(当指针被销​​毁时将为您调用delete)。只要有可能,您应该使用智能指针并使用vectorstring来避免使用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)