Destructor that calls a function that can throw ex

2019-03-25 13:19发布

问题:

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?

回答1:

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.)



回答2:

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");
    }
}


回答3:

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



回答4:

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.



回答5:

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.