在什么情况下不会调用C ++析构函数?

时间:2010-07-05 12:39:52

标签: c++ exception signals destructor exit

我知道我的析构函数在堆栈的正常展开时被调用,并且在抛出异常时调用,但是在调用exit()时不会。

还有其他情况我的析构函数不会被调用吗?信号如SIGINT或SIGSEGV怎么样?我认为对于SIGSEGV,它们不会被调用,但对于SIGNINT它们是,我怎么知道哪些信号将展开堆栈?

是否有其他情况不会被召唤?

8 个答案:

答案 0 :(得分:47)

  
    

是否还有其他情况导致他们的[析构函数]不会被调用?

  
  1. 跳远:这些会干扰自然堆栈展开过程,并且经常会导致C ++中出现未定义的行为。
  2. 过早退出(你已经指出了这些,但值得注意的是,由于异常被抛出已经堆叠展开而导致未定义的行为,这就是为什么我们永远不应该抛出dtors)
  3. 从构造函数中抛出不会为类调用dtor。这就是为什么,如果你在ctor中分配由几个不同指针(而不是智能指针)管理的多个内存块,你需要使用函数级try块或避免使用初始化列表并在ctor中有一个try / catch块body(或者更好的是,只使用像scoped_ptr这样的智能指针,因为到目前为止,在初始化程序列表中成功初始化的任何成员都将被销毁,即使不会调用类dtor)。
  4. 正如所指出的,当通过基指针删除类时无法使dtor成为虚拟脚本可能无法调用子类dtors(未定义的行为)。
  5. 未能为运算符new / new []调用调用匹配运算符delete / delete [](未定义的行为 - 可能无法调用dtor)。
  6. 在deallocate部分中使用带有自定义内存分配器的placement new时,无法手动调用dtor。
  7. 使用memcpy这样的函数,它只将一个内存块复制到另一个内存块而不调用copy ctors。 mem *函数在C ++中是致命的,因为它们推翻了类的私有数据,覆盖了vtable等。结果通常是未定义的行为。
  8. 在不完整类型上实例化某些智能指针(auto_ptr),请参阅此discussion

答案 1 :(得分:7)

C ++标准没有说明必须如何处理特定信号 - 许多实现可能不支持SIGINT等。如果exit()abort()或{{1}将不会调用析构函数}} 叫做。

编辑:我刚刚通过C ++标准进行了快速搜索,但是我找不到任何指定信号如何与对象生命周期交互的东西 - 也许是一个比我有更好标准的人找东西?

进一步修改:在回答其他问题时,我在标准中发现了这一点:

  

退出范围时(但是   完成),析构函数(12.4)是   要求所有构造的对象   具有自动存储持续时间   (3.7.2)(命名对象或临时对象)   在该范围内声明的,在   他们的相反顺序   宣言。

所以似乎必须在收到信号时调用析构函数。

答案 2 :(得分:3)

信号本身不会影响当前线程的执行,从而影响析构函数的调用,因为它是一个具有自己堆栈的不同执行上下文,你的对象不存在的地方。这就像一个中断:它在执行上下文之外的某个地方处理,如果处理,控制权将返回给你的程序。

与多线程相同,C ++ 语言不知道信号的概念。这两者完全相互正交,由两个不相关的标准规定。它们如何相互作用取决于实施,只要它不违反任何标准。

作为旁注,另一种情况是,当构造函数抛出异常时,不会调用对象的析构函数。不过,成员的破坏者仍将被召唤。

答案 3 :(得分:3)

他们不会被调用的另一种情况是你使用多态并且没有使你的基础析构函数成为虚拟。

答案 4 :(得分:2)

如标准所述,

abort终止程序而不对自动或静态存储持续时间的对象执行析构函数。对于其他情况,您应该阅读特定于实现的文档。

答案 5 :(得分:2)

如果函数或方法具有throws规范,并抛出规范未涵盖的内容,则默认行为是立即退出。堆栈没有展开,也没有调用析构函数。

POSIX信号是特定于操作系统的构造,并且没有C ++对象范围的概念。通常你不能对信号做任何事情,除了可能,捕获它,设置一个全局标志变量,然后在信号处理程序退出后在你的C ++代码中处理它。

最新版本的GCC允许您从同步信号处理程序中抛出异常,这会导致预期的展开和销毁过程。这是非常操作系统和编译器,但

答案 6 :(得分:2)

这里有很多答案,但仍然不完整!

我发现了另一个没有执行析构函数的情况。当跨越库边界捕获异常时,总会发生这种情况。

在此处查看更多详情:

Destructors not executed (no stack unwinding) when exception is thrown

答案 7 :(得分:1)

基本上有两种情况,其中调用析构函数:在函数结束时(或异常情况下)堆栈展开,如果有人(或引用计数器)调用delete。

在静态对象中可以找到一种特殊情况 - 它们在程序结束时通过at_exit被破坏,但这仍然是第二种情况。

at_exit通过哪个信号可能取决于,kill -9会立即终止进程,其他信号会告诉它退出但是究竟是如何依赖于信号回调。