在什么情况下C ++析构函数不会被调用?
我知道我的析构函数是在堆栈的正常展开和引发exception时调用的,而不是在调用exit()时调用的。
有没有其他的情况下,我的析构函数不会被调用? 信号如SIGINT或SIGSEGV怎么样? 我认为对于SIGSEGV来说,它们不会被调用,但是对于SIGNINT来说,我怎样才能知道哪些信号会将栈放开呢?
有没有其他的情况下,他们不会被叫?
有没有其他的情况下,他们[析构]不会被称为?
- 长时间跳转:这些干扰了自然堆栈展开过程,并且经常导致C ++中的未定义行为。
- 不成熟的退出(你已经指出了这一点,但值得注意的是,由于抛出exception导致已经堆栈展开的抛出会导致未定义的行为,这就是为什么我们不应该抛出dtors)
- 从构造函数中抛出不会为类调用dtor。 这就是为什么如果你在一个ctor中分配多个由多个不同的指针(而不是智能指针)pipe理的内存块,你需要使用函数级的try块或者避免使用初始值设定项列表,并且在ctor中有一个try / catch块(或者更好的是,只需使用一个像scoped_ptr这样的智能指针,因为即使类dtor不会被调用,任何已经成功初始化到初始化列表中的成员都将被销毁)。
- 正如所指出的,当通过基指针删除一个类时,如果不能虚拟出dtor,可能无法调用子类dtors(未定义的行为)。
- 无法为运算符new / new []调用(未定义的行为 – 可能无法调用dtor)调用匹配的运算符delete / delete []。
- 在deallocate部分中使用自定义内存分配器使用placement new时,无法手动调用dtor。
- 使用像memcpy这样的function,只复制一个内存块到另一个内存块,而不需要复制拷贝。 mem *函数在C ++中是致命的,因为它们推翻了类的私有数据,覆盖了vtable等。结果通常是未定义的行为。
- 实例化一些不完整types的智能指针(auto_ptr),请参阅此讨论
C ++标准没有提到如何处理特定的信号 – 许多实现可能不支持SIGINT
等。如果调用exit()
或abort()
或terminate()
则不会调用析构函数。
编辑:我刚刚通过C ++标准进行了快速search,找不到任何指定信号如何与对象生命周期交互的东西 – 也许有更好的标准的人比我更能find东西?
进一步编辑:在回答另一个问题时,我在标准中发现了这个问题:
在退出范围(但是已完成)时,对于在该范围内声明的具有自动存储持续时间(3.7.2)的所有构造对象(命名对象或临时对象),将按其声明的相反顺序调用析构函数(12.4)。
所以看来,必须收到信号才能调用析构函数。
另一个他们不会被调用的情况是,如果你正在使用多态,并没有使你的基本析构函数变成虚拟的。
一个信号本身不会影响当前线程的执行,因此也不会影响析构函数的调用,因为它是一个与自己的堆栈不同的执行上下文,不存在对象。 这就像一个中断:它在你的执行上下文之外的某个地方被处理,如果被处理,这个控制就返回到你的程序中。
和multithreading一样,C ++ 语言并不知道信号的概念。 这两个是完全正交的,由两个不相关的标准来规定。 只要不违反任何一个标准,它们如何相互作用取决于实施。
另外,另一种情况是当构造函数抛出一个exception时,对象的析构函数不会被调用。 尽pipe如此,成员的破坏者仍然会被调用。
如标准所述, abort
终止程序而不执行自动或静态存储持续时间的对象的析构器。 对于其他情况,您应该阅读实现特定的文档。
如果一个函数或者方法有一个throws规范,并且抛出了不在规范中的东西,那么默认的行为是立即退出。 栈不解开,析构函数不被调用。
POSIX信号是特定于操作系统的结构,没有C ++对象范围的概念。 一般来说,除了信号处理器之外,你不能对信号做任何事情,设置一个全局标志variables,然后在信号处理程序退出后在C ++代码中处理它。
GCC的最新版本允许您从同步信号处理程序中引发exception,这会导致预期的展开和销毁过程。 虽然这是非常操作的系统和编译器
这里有很多答案,但仍然不完整!
我发现了另外一个不执行析构函数的例子。 当exception通过库边界时,总会发生这种情况。
在这里看到更多细节:
当抛出exception时,析构函数没有执行(没有堆栈展开)
基本上有两种情况:调用析构函数:在函数结束时(或者在exception情况下),在堆栈上展开,如果有人(或者引用计数器)调用delete。
一个特殊情况是在静态对象中find – 它们在程序结束时通过at_exit被破坏,但这仍然是第二种情况。
离开at_exit的信号可能取决于,kill -9会立即终止进程,其他信号会告诉它退出,但是依赖于信号callback的确切程度。