views:

225

answers:

6

I'm pondering a question on Brainbench. I actually realised that I could answer my question easily by compiling the code, but it's an interesting question nonetheless, so I'll ask the question anyway and answer it myself shortly.

Take a look at this snippet:

Snippet from Brainbench

The question considers what happens when we throw from a destructor (which causes terminate() to be called). It's become clear to me by asking the question that the memory is indeed freed and the destructor is called, but, is this before or after throw is called from foo? Perhaps the issue here is that throw is used while the stack is unwinding that is the problem... Actually this is slightly confusing.

A: 

Object a is a stack object, so there is no dynamic memory to be freed. Once control goes out of the scope of foo(), the stack frame, and therefore the object, no longer exists.

Tim
Actually, I believe you could say that memory on the stack is still freed once out of scope even if it's not dynamic. However, that's not the question here, I'm also asking about use of `throw` in a destructor.
nbolton
A: 

To illustrate, here's what happens in Microsoft C++:

#include <iostream>

class A {
public:
    ~A() {
        std::cout << "in ~A" << std::endl;
        throw "error";
    }
};

void foo() {
    A a;
    std::cout << "in foo" << std::endl;
    throw "error";
}

int main() {

    try {
        foo();
    }
    catch (void *) {
        std::cout << "exit: 1" << std::endl;
        return 1;
    }
    std::cout << "exit: 0" << std::endl;
    return 0;
}

And the result:

>cpptest1.exe
in foo
in ~A

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

>
nbolton
Hehe. "contact the application's support team". I think g++'s error is a little more informative, especially if you don't have a support team.
Richard Pennington
@Richard Pennington +1 Haha, yeah; the g++ message is *maybe* less user friendly, but certainly way more programmer friendly.
nbolton
+1  A: 

If I'm not mistaken, once terminate is called, no (further) stack unwinding would occur.

terminate calls a handler function (which you can set with set_terminate):

The type of a handler function to be called by terminate() when terminating exception processing.
Required behavior: A terminate_handler shall terminate execution of the program without returning to the caller.
Default behavior: The implementation's default terminate_handler calls abort().

At least I don't know of a way to "terminate execution without returning to the caller" that would allow you to unwind the stack.

You can modify the example to see what you can expect:

#include <cstdio>

class A
{
    public:
        ~A() {
            puts("Entered A destructor");
            throw "error";
        }
};

void foo()
{
    A a, b;
    throw "error";
}

int main()
{
    try {
        foo();
    } catch (const char*) {
        return 1;
    }
}

Now there are two A instances, and the destructor of the second one is never called, because the execution was terminated as soon as the destructor of the first A finished and let another exception escape.

UncleBens
+12  A: 

It's become clear to me by asking the question that the memory is indeed freed and the destructor is called, but, is this before or after throw is called from foo?

This is what is happening:

  • foo() is called
  • An object a of type A is created on the stack
  • The next statement throws
  • Now, the dtor for a is called, which throws another exception
  • std::terminate is called -- which is nothing but abandoning the exception handling mechanism:

From C++0x draft:

15.5.1 The std::terminate() function

1 In the following situations exception handling must be abandoned for less subtle error handling techniques:

[...] — when the destruction of an object during stack unwinding (15.2) exits using an exception, or

2 In such cases, std::terminate() is called (18.7.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to std::terminate().

Note: Emphasis mine

dirkgently
+2  A: 

Here's what happens in g++:

#include <stdio.h>
class A {
public:
    ~A()
    {
        fprintf(stderr, "in ~A\n");
        throw "error";
    }
};

void foo()
{
    A a;
    fprintf(stderr, "in foo\n");
    throw "error";
}

int main()
{
    try {
        foo();
    }
    catch (const char*) {
        return 1;
    }
    return 0;
}


[~/ecc/ellcc/ecc] main% ./a.out
in foo
in ~A
terminate called after throwing an instance of 'char const*'
Abort
[~/ecc/ellcc/ecc] main% 

As you can see, the throw in foo happens first, then the throw in ~A causes the error.

Richard Pennington
@Richard Thanks, I updated my answer for MS C++ -- seems very similar; I thought the behaviour was undefined between platforms, but apparently not.
nbolton
+1  A: 

You got is slightly wrong and that's why you don't understand it. You see, throw in destructor is not causing teriminate() function to be called, it is a bad practice, but it is not fatal for program execution. What is fatal is that some code throws while there's still active exception. C++ can't decide what exception to propagate further, new one or old one and it can't propagate them both. It is considered fatal for program execution and that's why terminate is called.

So, you see, without throw in foo, terminate wouldn't be called but there will be an exception thrown from ~A. So, naturally, throw in foo has to be called first and then during the second throw everything breaks.

vava
+1 Ah, that makes things a little more clear.
nbolton