14

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Good
  • 306
  • 1
  • 17
  • Your exception gets copied when it's thrown. 1st destructor is from the `try` block, 2nd is the copied exception. – Richard Critten May 18 '15 at 17:09
  • possible duplicate of [Why is the destructor of the class called twice?](http://stackoverflow.com/questions/2627540/why-is-the-destructor-of-the-class-called-twice) – Seth May 18 '15 at 17:10
  • The exception temporary is copied, but this copy may be elided. If it is, you'll only see one destructor call: http://coliru.stacked-crooked.com/a/f1e5773144874712 – dyp May 18 '15 at 17:10
  • 3
    @wheatin That question is about the destructor of an object added to a collection, I don't think it's the same. – Barmar May 18 '15 at 17:11
  • What compiler are you using? – NathanOliver May 18 '15 at 17:13
  • BTW you should better catch exceptions using a `const` reference: `catch (const MyError& exc) {`, it's commonly appreciated best practice. – πάντα ῥεῖ May 18 '15 at 17:18
  • @NathanOliver Microsoft Visual C++ (2013) – Good May 18 '15 at 17:18

3 Answers3

11

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";
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
4

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
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
3

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
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111