假设我有这样一个类:
#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。
答案 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。