Destructor that calls a function that can throw exception in C++
Asked Answered
T

5

11

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?

Tarr answered 27/7, 2009 at 13:47 Comment(2)
Just for clarification, are you asking if it's ok if the destructor catches the exception, so it never leaves the destructor, or if it's ok to let it leave the destructor as long as it is caught outside?Penniepenniless
I'm asking whether it's OK if the exception stays in the D'tor.Tarr
P
21

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 during stack unwinding because another exception was thrown, std::terminate is called. So it is well-defined by the standard, but it's a really bad idea.)

Penniepenniless answered 27/7, 2009 at 13:49 Comment(12)
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.Durkee
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++.Penniepenniless
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.Choirboy
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.Penniepenniless
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++.Choirboy
true, and I should probably have used another (more specific) term.Penniepenniless
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).Chian
This principle also known as what happens in destructor stays in destructor.Broucek
-1 for deceiving as if std::terminate() is called if exception leaves destructor. Indeed, std::terminate() is called if stack unwinding is also in progress. Otherwise exception just leaves destructor and "operator delete" does not get called (so there is a memory leak for objects, allocated on heap).Jimjimdandy
@SergeRogatch "deceiving?" You mean it is an intentional lie to mislead people? What makes you think that?Penniepenniless
@jalf, no, I don't mean intentional or unintentional because I don't know. English is not my native language. Perhaps it would be better to say "misleading" or "giving a wrong idea", but that is much more to think or even to type. Sorry for inconveniences. I see that you've edited your answer so that now I can remove my -1.Jimjimdandy
@SergeRogatch no problem :) And yeah, misleading would be a better word. :) (And thanks for pointing out the error.)Penniepenniless
C
4

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");
    }
}
Chian answered 31/7, 2009 at 23:40 Comment(0)
J
2

You can find some examples here: https://software.intel.com/sites/products/documentation/doclib/iss/2013/sa-ptr/sa-ptr_win_lin/GUID-D2983B74-74E9-4868-90E0-D65A80F8F69F.htm

If an exception leaves destructor during stack unwinding of another exception being propagated, then std::terminate() is called.

When no stack unwinding is in progress, an exception can leave destructor without std::terminate() getting called. However, for objects allocated on heap this will result in memory leak because "operator delete" will not be called for the object who throws exception out of its destructor. Surprisingly, the destructor of base class still gets called in this case: What happens to base class destructor if a derived class destructor throws an exception

If the exception is catched inside the destructor (so that the exception does not leave the destructor), then no problem even if stack unwinding of another exception is in progress. This case is described more deeply here: http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

Jimjimdandy answered 30/5, 2015 at 9:40 Comment(0)
C
0

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.

Catchascatchcan answered 27/7, 2009 at 18:6 Comment(1)
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 propagatePenniepenniless
N
-1

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.

Noon answered 27/7, 2009 at 14:44 Comment(1)
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.Subnormal

© 2022 - 2024 — McMap. All rights reserved.