我有以下计划:
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class MyError : public runtime_error
{
public:
MyError(string mess = "");
~MyError(void);
};
MyError::MyError(string mess) : runtime_error(mess)
{
cout << "MyError::MyError()\n";
}
MyError::~MyError(void)
{
cout << "MyError::~MyError\n";
}
int main(void)
{
try {
throw MyError("hi");
}
catch (MyError& exc) {
cout << exc.what() << endl;
}
cout << "goodbye\n";
return 0;
}
打印以下内容:
MyError::MyError()
MyError::~MyError
hi
MyError::~MyError
goodbye
为什么异常的析构函数( ~MyError())被调用两次?
我假设throw创建了一个新对象,但我不明白为什么要调用类析构函数。
答案 0 :(得分:11)
如果您检测异常的副本或移动构造函数,您会发现它在处理程序之前被调用一次。有一个临时的异常对象,其中复制/移动了抛出的表达式,并且处理程序中的引用将绑定到此异常对象。 C ++ 14 15.1 / 3 +
因此,代码产生的执行看起来像这样(伪C ++):
// throw MyError("hi"); expands to:
auto tmp1 = MyError("hi");
auto exceptionObject = std::move(tmp1);
tmp1.~MyError();
goto catch;
// catch expands to:
MyError& exc = exceptionObject;
cout << exc.what() << endl;
// } of catch handler expands to:
exceptionObject.~MyError();
// normal code follows:
cout << "goodbye\n";
答案 1 :(得分:4)
因为您的编译器无法将副本从异常处理机制管理的临时对象中删除。
从概念上讲,MyError("hi")
创建一个临时的,它将在语句结束时被销毁(在处理异常之前)。 throw
将抛出的值复制到其他位置,它将持续到处理异常之后。如果抛出的值是临时的,那么一个体面的编译器应该忽略副本并直接初始化抛出的值;显然,你的编译器没有这样做。
我的编译器(GCC 4.8.1)做得更好:
MyError::MyError()
hi
MyError::~MyError
goodbye
答案 2 :(得分:3)
正在复制您的例外。如果您使用复制文件,您可以看到:
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class MyError : public runtime_error
{
public:
MyError(MyError const &e) : runtime_error("copy") { std::cout << "Copy MyError"; }
MyError(string mess = "");
~MyError(void);
};
MyError::MyError(string mess) : runtime_error(mess) {
cout << "MyError::MyError()\n";
}
MyError::~MyError(void) {
cout << "MyError::~MyError\n";
}
int main(void) {
try {
throw MyError("hi");
}
catch (MyError& exc) {
cout << exc.what() << endl;
}
cout << "goodbye\n";
return 0;
}
结果:
MyError::MyError()
Copy MyError
MyError::~MyError
copy.what()
MyError::~MyError
goodbye