3

我不禁阅读了大量关于析构函数的论坛帖子并完全感到困惑。

有人说每次调用都要调用析构函数(with delete)一次。有人说析构函数会在各种情况下自动被调用,即 当指针重新分配时,当对象超出范围时。有些人建议指针超出范围,同时作为对象作为其前自我的副本存在的返回值,(这是否需要显式销毁,因为它最初是用?newnew

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

我似乎无法理解关于调用析构函数序列的讨论,即调用 1)是否源自基超类并级联到特定类,在途中调用所有虚拟化析构函数,2)源自实例化类并向上移动到超类,或者 3) 源自该类在超出范围时具有的特定强制转换,然后遍历实例化类和基类。做级联析构函数

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

4

3 回答 3

8

有 3 种类型的分配以不同的方式调用析构函数:

自动分配

这些对象驻留在自动内存中(通常是堆栈):

int main()
{
  A a;
  //...
}

当超出范围(关闭)时,会a自动调用析构函数。a}

动态分配

对象驻留在动态内存(堆)中。它们被分配new并为了调用 dstructor,您需要调用delete

int main()
{
  A* a = new A;
  delete a;    //destructor called
}

在这种情况下,可能建议您NULL在. 关于这一点有两种思想流派(我个人不建议这样做)。这样做的动机是,如果您不将其设置为. 哪个是对的。但是如果你再次调用,那已经是一个错误或逻辑中的错误,不应该通过使代码看起来正确运行来掩盖它。adeletedeleteaNULLdelete

静态分配

对象驻留在static内存中。无论它们分配在哪里,程序结束时都会自动调用析构函数:

A a; //namespace scope

int main()
{
}

在这里,As 析构函数在程序终止时调用,在main完成之后。

于 2013-08-05T09:28:22.593 回答
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

于 2013-08-05T10:01:46.710 回答
0

如果您的代码调用new,那么它也应该调用delete,是的。除非您使用智能指针(delete当指针被破坏时会调用您)。只要有可能,您应该使用智能指针并使用vectororstring避免手动分配内存使用new- 如果您不调用new,则无需担心确保delete被调用 -> 没有内存泄漏,并且对象没有问题在错误的时间被销毁等

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;

C然后delete调用析构函数。的析构函数BC析构函数调用,析构函数A被析构函数调用B。这是自动的,编译器将“确保发生这种情况”。

如果我们不调用 new:

... 
{
    C c;
    ...
}   // Destructor for C gets called here (and B and A as describe above)
于 2013-08-05T09:35:07.957 回答