是否会调用抛出异常的C ++类的析构函数?

时间:2010-06-27 23:51:13

标签: c++ resources destructor acquisition

假设我有这样一个类:

#include <iostream>

using namespace std;

class Boda {
    private:
        char *ptr;

    public:
        Boda() {
            ptr = new char [20];
        }
        ~Boda() {
            cout << "calling ~Boda\n";

            delete [] ptr;
        }

        void ouch() {
            throw 99;
        }
};

void bad() {
    Boda b;
    b.ouch();
}

int main() {
    bad();
}

似乎永远不会调用析构函数~Boda,因此ptr资源永远不会被释放。

以下是该计划的输出:

terminate called after throwing an instance of 'int'
Aborted

所以我的问题的答案似乎是No

但我认为当抛出异常时堆栈被解开了?为什么Boda b对象在我的示例中没有被破坏?

请帮我理解这个资源问题。我想在将来写出更好的节目。

此外,这是所谓的RAII

谢谢,Boda Cydo。

3 个答案:

答案 0 :(得分:8)

如果异常没有被捕获到任何地方,那么C ++运行时可以直接终止程序,而无需进行任何堆栈展开或调用任何析构函数。

但是,如果在bad()的调用周围添加try-catch块,您将看到被调用的Boda对象的析构函数:

int main() {
    try {
      bad();
    } catch(...) {  // Catch any exception, forcing stack unwinding always
      return -1;
    }
}

RAII 意味着动态(堆)分配的内存始终由自动(堆栈)分配的对象拥有,该对象在对象析构时解除分配。这依赖于保证在自动分配的对象超出范围时调用析构函数,无论是由于正常返回还是由于异常。

对于RAII,这种角落行为通常不是问题,因为通常你想要析构函数运行的主要原因是释放内存,并且当程序终止时,所有内存都会返回给操作系统。但是,如果你的析构函数做了一些更复杂的事情,比如可能删除磁盘上的锁定文件或某些东西,那么程序在崩溃时是否会调用析构函数会有所不同,你可能想要试试你的main -catch块捕获所有内容(仅在异常时退出),以确保堆栈在终止之前始终展开。

答案 1 :(得分:2)

如果构造函数中发生异常,则不会运行析构函数。

如果在您的示例中的另一个方法中引发异常,它将在必要时运行(如果在某处处理异常)。但是当程序终止时,这里不需要调用析构函数,行为取决于编译器...

RAII的想法是构造函数分配ressources和析构函数释放它们。如果构造函数中发生异常,则没有简单的方法可以知道分配的资源和不分配的资源(这取决于构造函数中发生异常的确切位置)。您还应该记住,如果构造函数失败,则说出调用它以引发异常分配的内存的唯一方法是释放(堆栈展开或堆分配的内存),就好像它永远不会分配

解决方案很明显:如果构造函数内部可能发生任何异常,则必须捕获它并在必要时释放已分配的资源。它实际上可能是一些带有析构函数的重复代码,但这不是一个大问题。

在析构函数中,你不应该引发异常,因为它会导致堆栈展开时出现大问题。

在任何其他方法中,根据需要使用异常,但不要忘记在某处处理它们。一个未处理的感觉可能比没有例外更糟糕。我知道有些程序不能处理一些小错误的异常......而且崩溃只会发出警告。

答案 2 :(得分:1)

尝试刷新流 - 您将看到确实调用了析构函数:

cout << "calling ~Boda" << endl;

这是I / O的缓冲,它将打印输出延迟到程序终止在实际输出之前切入的程度。

编辑:

以上适用于已处理的例外。对于未处理的异常,标准不指定堆栈是否已展开。另请参阅this SO question