C++ 的析构函数是否有效?抛出异常的类被调用?
假设我有一个这样的类:
#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
所以看来我的问题的答案是否
。
但我认为抛出异常时堆栈会被释放?为什么在我的示例中 Boda b
对象没有被破坏?
请帮助我理解这个资源问题。我想将来写出更好的程序。
另外,这就是所谓的RAII
吗?
谢谢,博达·西多。
Suppose I have a class like this:
#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();
}
It seems that destructor ~Boda
never gets called, thus the ptr
resource never get freed.
Here is the output of the program:
terminate called after throwing an instance of 'int'
Aborted
So it seems the answer to my question is No
.
But I thought that the stack got unwound when an exception got thrown? Why didn't Boda b
object get destructed in my example?
Please help me understand this resource problem. I want to write better programs in the future.
Also, is this the so called RAII
?
Thanks, Boda Cydo.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果异常没有在任何地方被捕获,那么 C++ 运行时可以直接终止程序,而不进行任何堆栈展开或调用任何析构函数。
但是,如果您在对
bad()
的调用周围添加一个 try-catch 块,您将看到正在调用的Boda
对象的析构函数:RAII< /strong> 意味着动态(堆)分配的内存始终由自动(堆栈)分配的对象拥有,该对象在对象解构时会释放它。这依赖于当自动分配的对象超出范围时(无论是由于正常返回还是由于异常)将调用析构函数的保证。
对于 RAII,这种极端情况行为通常不是问题,因为通常您希望析构函数运行的主要原因是释放内存,并且当您的程序终止时,所有内存都会返回给操作系统。但是,如果您的析构函数执行更复杂的操作,例如删除磁盘上的锁定文件或其他内容,那么程序崩溃时是否调用析构函数会产生影响,您可能需要包装您的
main
在捕获所有内容的 try-catch 块中(无论如何都只是在出现异常时退出),以确保堆栈始终在终止之前展开。If the exception is not caught anywhere, then the C++ runtime is free to go straight to terminating the program without doing any stack unwinding or calling any destructors.
However, if you add a try-catch block around the call to
bad()
, you will see the destructor for the theBoda
object being called:RAII means that dynamically (heap) allocated memory is always owned by an automatically (stack) allocated object that deallocates it when the object destructs. This relies on the guarantee that the destructor will be called when the automatically-allocated object goes out of scope, whether due to a normal return or due to an exception.
This corner-case behavior is normally not a problem with respect to RAII, since usually the main reason that you want the destructors to run is to free memory, and all memory is given back to the OS when your program terminates anyway. However if your destructors do something more complicated, like maybe remove a lock file on disk or something, where it would make a difference whether the program called destructors or not when crashing, you may want to wrap your
main
in a try-catch block that catches everything (only to exit on exception anyway) just to ensure that the stack always unwinds before terminating.如果构造函数中发生异常,析构函数将不会运行。
如果有必要(如果在某处处理异常),如果在另一个方法中引发异常(如您的示例中所示),它将运行。但是当程序终止时,这里不需要调用析构函数,行为取决于编译器...
RAII 的思想是构造函数分配资源,析构函数释放它们。如果构造函数中发生异常,则没有简单的方法可以知道哪些资源已分配,哪些资源未分配(这取决于构造函数中发生异常的确切位置)。您还应该记住,如果构造函数失败,则调用它以引发异常的唯一方法是释放分配的内存(堆栈展开或堆分配的内存),就好像它从未被释放一样分配。
解决方案很明显:如果构造函数内可能发生任何异常,您必须捕获它并在必要时释放分配的资源。它实际上可能是一些带有析构函数的重复代码,但这不是一个大问题。
在析构函数中,您不应该引发异常,因为它可能会导致堆栈展开出现大问题。
在任何其他方法中,可以根据需要使用异常,但不要忘记在某处处理它们。未处理的异常可能比没有异常更糟糕。我知道有些程序不处理某些小错误的异常...并且会因仅发出警告的错误而崩溃。
The destructor won't be run if an exception occurs in the constructor.
It will be run if necessary (if exception is handled somewhere) if exception is raised in another method like in your example. But as the program is terminated, calling the destructor is not necessary here and behavior depends of compiler...
The idea of
RAII
is that constructor allocates ressources and destructor frees them. If an exception occurs in constructor, there is no simple way to know wich ressources where allocated and which were not (it depends on the exact place in the constructor where exception occured). You should also remember that if a constructor fails, the only way to say it to caller it to raise an exception and allocated memory is freed (either stack unwinding, or heap allocated memory) as if it were never allocated.The solution is obvious : if any exception may occur inside a constructor, you have to catch it and free allocated ressources if necessary. It may actually be some duplicated code with destructor, but that's not a big problem.
In destructor you should not raise exceptions, as it can lead to big troubles with stack unwinding.
In any other method, use exceptions as you like, but do not forget to handle them somewhere. An unhandled excception may be worse than no exception at all. I know of some programs that does not handle exceptions for some minor errors... and crash for errors that should only issue a warning.
尝试刷新流 - 您将看到析构函数确实被调用:
它是 I/O 的缓冲,将打印输出延迟到程序终止在实际输出之前切入的点。
编辑:
以上内容适用于已处理的异常。对于未处理的异常,标准没有指定堆栈是否展开。另请参阅这个问题。
Try flushing the stream - you will see that the destructor is indeed called:
It's the buffering of the I/O that delays the printout to the point that program termination cuts in before actual output.
Edit:
The above holds for handled exceptions. With unhandled exceptions the standard does not specify whether stack is unwound or not. See also this SO question.