tags:

views:

254

answers:

6

Looking on the internet for C++ brainteasers, I found this example:

#include <iostream>

using namespace std;

class A {
public:
    A()
    {
     cout << "A::A()" << endl;
    }

    ~A()
    {
     cout << "A::~A()" << endl;
     throw "A::exception";
    }
};

class B {
public:
    B()
    {
     cout << "B::B()" << endl;
     throw "B::exception"; // <- crashes here
    }

    ~B()
    {
     cout << "B::~B()";
    }
};

int main(int, char**) {
    try
    {
     cout << "Entering try...catch block" << endl;

     A objectA;
     B objectB;

     cout << "Exiting try...catch block" << endl;
    }
    catch (const char* ex)
    {
     cout << ex << endl;
    }

    return 0;
}

This is what I thought the program would do:

  1. A::A() will be output to screen when the constructor of objectA is called. Object A is constructed successfully.
  2. B::B() will be output to screen when the constructor of objectB is called.
  3. The constructor of B then throws an exception. Object B is not constructed successfully.
  4. Destructor of objectB is not called as the constructor never completed successfully.
  5. Destructor of objectA will be called as the object goes out of scope when the try block is exited.

However, when I ran the program, it actually crashed at the line marked with <-. Could anybody explain what exactly was going on at that point?

+3  A: 

Golden rule in C++ - destructors should never throw exceptions. Ignoring this rule will lead to undefined behaviour in all sorts of situations.

anon
I think it is well defined in all situation (could be wrong). Now admittedly its not a good idea.
Martin York
@Martin The C++ Standard specifies that objects stored in containers must not have destructors that throw - it doesn't define what happens if they do.
anon
+2  A: 

A::~A() is called when the try block is exited, precisely because the object goes out of scope. This is why RAII works --- it depends on destructors being called regardless of why the scope is exited.

A::~A() then throws an exception. As B::B()'s exception is still being thrown up the stack, this crashes the program.

Dave Hinton
Does not crash: terminate() is called.
Martin York
+2  A: 

Your should NEVER throw an exception in destructor. Take a look at this question why.

Serge
+6  A: 

When B::B() throws an exception stack unwinding begins. A::~A() is called and throws another exception that is not catched inside A::~A().

If another exception leaves a destructor while stack unwinding is in progress terminate() is called and that looks like a program crash.

sharptooth
+10  A: 

If you are really coding, not just brainteasing never ever throw exception from destructor. If an exception is thrown during stack-unwinding, terminate() is called. In your case destructor of A has thrown while processing exception that was thrown in B's constructor.

EDIT: To be more precise (as suggested in comments) - never ever let exception escape destructor. Exceptions that are caught inside destructor make no problem. But if during stack unwinding program has to deal with two exceptions - the one that caused stack unwinding and the one that escaped destructor during unwinding, std::terminate() goes.

Tadeusz Kopec
Thank you. I actually learnt something from these brainteasers.So, did the program crash because an exception is thrown during stack-unwinding, or because an exception is thrown while handling another exception?
Andy
15.2.3: "The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [ Note: If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor." Term "processing exception" is not precise, one can understand by it a process of stack unwinding, when another exception is not allowed, or process of exception-handling (a catch block), where throwing exceptions is OK.
Tadeusz Kopec
Correction: If an exception ESCAPES a destructor while another exception is propagating (stack unwinding) then terminate() is called. There is no problems with using exceptions in a destructor just make sure you catch them before the destructor completes.
Martin York
+3  A: 

The code crashes because B's C'tor throws an exception, and then starts the "Stack Unwinding" procedure: all the local objects are destructed, dus, A's D'tor is called, and throws an exception during the Stack unwinding, and then "abort" is called, because there can't be two exceptions at the same time...

Conclusion:

Never throw an exception from a D'tor, because you might be throwing it during Stack Unwinding.

Gal Goldman