views:

820

answers:

4

I know that I shouldn't throw exceptions from a destructor.

If my destructor calls a function that can throw an exception, is it OK if I catch it in the destructor and don't throw it further? Or can it cause abort anyway and I shouldn't call such functions from a destructor at all?

+14  A: 

Yes, that's legal. An exception must not escape from the destructor, but whatever happens inside the destructor, or in functions it calls, is up to you.

(Technically, an exception can escape from a destructor call as well. If that happens, std::terminate is called. So it is well-defined by the standard, but it's a really bad idea.)

jalf
Sorry for nitpicking, but I would use a term other than "legal". Throwing an exception in the destructor is also "legal", i. e. it will compile and run. But it is a bad practice that will cause unpleasant effects.
Dima
Yes and no. You're right it's technically legal to throw an exception from a destructor (`std::terminate` is called then). But your definitoin of "legal" is wrong. Just because something compiles and runs in no way makes it legal C++.
jalf
I'm not sure what you want legal to mean, but I'm with Dima on this one. Although C++ doesn't use the work legal, it does define a *well-formed program* (probably the closest to what I think of as 'legal'), *unspecified behaviour* and *undefined behaviour*. Allowing an exception to propogate out of a destructor can happen in a well-formed program and the behaviour is neither undefined, nor unspecified. That doesn't stop it from being an almost universally undesirable behaviour.
Charles Bailey
Oh, I agree with his point that throwing an exception from a destructor is well-defined. I just mean that "it compiles and runs" is not the same as "legal" by any sane definition of the word.
jalf
Oh right, yes, I definitely agree with that, although I'm also fast coming around to the opinion that the word 'legal' isn't very helpful w.r.t. C++.
Charles Bailey
true, and I should probably have used another (more specific) term.
jalf
There is absolutely no problem with an exception escaping a destructor under normal conditions (std::terminate() is NOT called). It is only a problem if another exception is already propogating (std::terminate() is called), this is because the runtime can not handle the concept of two parallel exceptions propogating simultaniously (or the C++ designers could not think of a logical way to handle the situation).
Martin York
A: 

You may find this page from C++ FAQ Lite to be informative. The basic answer is, "don't do it." Where exactly do you plan to catch this exception? Whatever you plan on doing when you catch that exception, just do it with a function call instead or something (e.g. log it or set a flag to warn the user or whatever). Throwing exceptions from the destructor runs the risk of causing your entire program to terminate.

Brian
How is this relevant to the question? If he calls a function which can throw std::bad_alloc, what would be the alternative? Write a "wrapper" function? That's not a solution, that just hides the exception in another layer.
MSalters
A: 

Simple answer, never allow an exception from a dtor!

The complicated answer. You only get really nailed if the exception escapes the dtor while another exception is active. The normal case for this is when you are already unwinding the stack from another exception and the object in question is destroyed. In that case if the exception escapes the dtor then std::terminate is called, note you can put in your own handler for std::terminate by calling std::set_terminate. The default implementation of std::terminate is to call abort.

To complicate things more, most functions that want to make any guarantee about their exception safety, mainly the basic guarantee or the strong guarantee, rely on the underlying types to themselves not throw in their dtor*

The real question is, what state would your program be in when this error occurs? How can you recover? Where should this recovery be handled? You need to look at your specific case and work these issues out. Sometimes it's just fine to catch the exception and ignore it. Other times you need to raise some red flags.

So the answer is: it allowed by C++ to throw an exception in a dtor, but you shouldn't ever allow it to escape.

*Here's a brief synopsis of the exception guarantees (here's a much longer article)

  1. Recap: Briefly define the Abrahams exception safety guarantees (basic, strong, and nothrow).

The basic guarantee is that failed operations may alter program state, but no leaks occur and affected objects/modules are still destructible and usable, in a consistent (but not necessarily predictable) state.

The strong guarantee involves transactional commit/rollback semantics: failed operations guarantee program state is unchanged with respect to the objects operated upon. This means no side effects that affect the objects, including the validity or contents of related helper objects such as iterators pointing into containers being manipulated.

The nothrow guarantee means that failed operations will not happen. The operation will not throw an exception.

Matt Price
as I understand the question, he's not asking about exceptions leaving the dtor at all, but simply whether it is ok for the dtor to call a function which throws an exception, as long as the dtor catches the exception so it doesn't propagate
jalf
A: 

Yes.

Look at the std::fstream class in the standard library for an example.

  • close() could potentially throw an exception.
  • The destroctor can call close() but the destructor does not throw (it will swallow any exceptions).

The concept is that if the destructor calls any methods that can throw then these methods should be public. Thus if the user of your object wants to check for exceptions they can use the public methods and handle the exception. If they do not care about the exception then just let the destructor handle the problem.

Going back to the std::fstream example.

{
    std::fstream   text("Plop");
    // Load Text.

    // I don't care if the close fails.
    // So let the destructor handle it and discard exceptions
}



{
    // If this fails to write I should at least warn the user.
    // So in this case I will explicitly try and close it.
    try
    {
        std::ofstram    password("/etc/password");
        // Update the password file.

        password.close();
    }
    catch(...)
    {
          Message.ShowDialog("You failed to update the Password File");
    }
}
Martin York