Подтвердить что ты не робот

Почему деструктор исключения называется дважды?

У меня есть следующая программа:

#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 создает новый объект, но я не понимаю, почему вызывается деструктор класса.

4b9b3361

Ответ 1

Если вы создаете экземпляр исключений или перемещаете конструктор, вы найдете его один раз перед обработчиком. Там есть временный объект исключения, в который скопировано/перемещено брошенное выражение, и именно этот объект исключения привязывается к ссылке в обработчике. С++ 14 15.1/3 +

Итак, выполнение в результате вашего кода выглядит примерно так (псевдо-С++):

// 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";

Ответ 2

Потому что ваш компилятор не может удалить копию из временного объекта исключения, управляемого механизмом обработки исключений.

Концептуально MyError("hi") создает временное, которое будет уничтожено в конце инструкции (до того, как будет обработано исключение). throw копирует брошенное значение где-то в другом месте, где оно будет сохраняться до тех пор, пока не будет обработано исключение. Если брошенное значение является временным, то достойный компилятор должен исключить копию и инициализировать полученное значение напрямую; по-видимому, ваш компилятор этого не делал.

Мой компилятор (GCC 4.8.1) выполняет более качественную работу:

MyError::MyError()
hi
MyError::~MyError
goodbye

Ответ 3

Ваше исключение копируется. Если вы копируете копию ctor, вы можете увидеть это:

#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