6

假设我有这样的课程:

#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吗?

谢谢,博达赛多。

4

3 回答 3

9

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

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

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

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

对于 RAII,这种极端情况行为通常不是问题,因为通常您希望析构函数运行的主要原因是释放内存,并且当您的程序无论如何终止时,所有内存都会返回给操作系统。但是,如果你的析构函数做一些更复杂的事情,比如可能删除磁盘上的锁定文件或其他东西,在崩溃时程序是否调用析构函数会有所不同,你可能希望将你main的 try-catch 包装在一个捕获块中一切(无论如何都只在异常时退出)只是为了确保堆栈在终止之前总是展开。

于 2010-06-27T23:53:05.620 回答
2

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

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

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

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

在析构函数中你不应该引发异常,因为它会导致堆栈展开的大麻烦。

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

于 2010-06-28T00:29:38.603 回答
1

尝试刷新流 - 你会看到析构函数确实被调用了:

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

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

编辑:

以上适用于已处理的异常。对于未处理的异常,标准没有指定堆栈是否展开。另请参阅此 SO 问题

于 2010-06-27T23:55:06.160 回答