Good
Good

Reputation: 436

Why is the destructor of an exception called twice?

I have following program:

#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;
}

Which prints the following:

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

Why is the destructor of the exception (~MyError()) called twice?

I assumed that throw creates a new object, but I do not understand why the class destructor is called.

Upvotes: 14

Views: 832

Answers (3)

Mike Seymour
Mike Seymour

Reputation: 254411

Because your compiler is failing to elide the copy from the temporary to the exception object managed by the exception handling mechanism.

Conceptually, MyError("hi") creates a temporary, which will be destroyed at the end of the statement (before the exception can be handled). throw copies the thrown value somewhere else, where it will persist until after the exception has been handled. If the thrown value is a temporary, then a decent compiler should elide the copy and initialise the thrown value directly; apparently, your compiler didn't do that.

My compiler (GCC 4.8.1) does a rather better job:

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

Upvotes: 4

Jerry Coffin
Jerry Coffin

Reputation: 490008

Your exception is being copied. If you instrument the copy ctor, you can see this:

#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;
}

Result:

MyError::MyError()
Copy MyError
MyError::~MyError
copy.what()
MyError::~MyError
goodbye

Upvotes: 3

If you instrument the exception's copy or move constructor, you'll find it's called once before the handler. There's a temporary exception object into which the thrown expression is copied/moved, and it is this exception object to which the reference in the handler will bind. C++14 15.1/3+

So the execution resulting from your code looks something like this (pseudo-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";

Upvotes: 11

Related Questions