为什么异常的析构函数会被调用两次?

14
我有如下程序:
#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 会创建一个新对象,但我不明白为什么类的析构函数会被调用。


当异常被抛出时,它会被复制。第一个析构函数来自try块,第二个是复制的异常。 - Richard Critten
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Seth
异常 temporary 被复制,但是这个副本可能会被省略。如果被省略了,你只会看到一个析构函数调用:http://coliru.stacked-crooked.com/a/f1e5773144874712 - dyp
3
这个问题是关于添加到集合中的对象的析构函数,我认为这不是同一个问题。 - Barmar
你在使用哪个编译器? - NathanOliver
显示剩余2条评论
3个回答

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
C++编译器在这种情况下是否允许执行复制省略?如果是,为什么它不执行呢? - Random832
@Random832 是的,在这种情况下允许进行复制省略。为什么VS不这样做是任何人都猜不到的——也许是因为OP的优化标记没有使其这样做? - Angew is no longer proud of SO

4
由于编译器无法消除从临时对象到异常处理机制管理的异常对象的复制,因此出现了编译器失败的情况。
从概念上讲,“MyError(“hi”)”创建一个临时对象,在语句结尾处被销毁,(在异常被处理之前)。throw将抛出值复制到其他位置,并在异常处理后保持存在。如果抛出的值是临时的,则优秀的编译器应该取消复制并直接初始化抛出的值;显然,您的编译器没有这样做。
我的编译器(GCC 4.8.1)做得相当好:
MyError::MyError()
hi
MyError::~MyError
goodbye

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接