throwing exceptions out of a destructor

2018-12-31 04:34发布

Most people say never throw an exception out of a destructor - doing so results in undefined behavior. Stroustrup makes the point that "the vector destructor explicitly invokes the destructor for every element. This implies that if an element destructor throws, the vector destruction fails... There is really no good way to protect against exceptions thrown from destructors, so the library makes no guarantees if an element destructor throws" (from Appendix E3.2).

This article seems to say otherwise - that throwing destructors are more or less okay.

So my question is this - if throwing from a destructor results in undefined behavior, how do you handle errors that occur during a destructor?

If an error occurs during a cleanup operation, do you just ignore it? If it is an error that can potentially be handled up the stack but not right in the destructor, doesn't it make sense to throw an exception out of the destructor?

Obviously these kinds of errors are rare, but possible.

16条回答
旧时光的记忆
2楼-- · 2018-12-31 04:53

The real question to ask yourself about throwing from a destructor is "What can the caller do with this?" Is there actually anything useful you can do with the exception, that would offset the dangers created by throwing from a destructor?

If I destroy a Foo object, and the Foo destructor tosses out an exception, what I can reasonably do with it? I can log it, or I can ignore it. That's all. I can't "fix" it, because the Foo object is already gone. Best case, I log the exception and continue as if nothing happened (or terminate the program). Is that really worth potentially causing undefined behavior by throwing from a destructor?

查看更多
其实,你不懂
3楼-- · 2018-12-31 04:54

Set an alarm event. Typically alarm events are better form of notifying failure while cleaning up objects

查看更多
美炸的是我
4楼-- · 2018-12-31 04:57

Throwing an exception out of a destructor is dangerous.
If another exception is already propagating the application will terminate.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

This basically boils down to:

Anything dangerous (i.e. that could throw an exception) should be done via public methods (not necessarily directly). The user of your class can then potentially handle these situations by using the public methods and catching any potential exceptions.

The destructor will then finish off the object by calling these methods (if the user did not do so explicitly), but any exceptions throw are caught and dropped (after attempting to fix the problem).

So in effect you pass the responsibility onto the user. If the user is in a position to correct exceptions they will manually call the appropriate functions and processes any errors. If the user of the object is not worried (as the object will be destroyed) then the destructor is left to take care of business.

An example:

std::fstream

The close() method can potentially throw an exception. The destructor calls close() if the file has been opened but makes sure that any exceptions do not propagate out of the destructor.

So if the user of a file object wants to do special handling for problems associated to closing the file they will manually call close() and handle any exceptions. If on the other hand they do not care then the destructor will be left to handle the situation.

Scott Myers has an excellent article about the subject in his book "Effective C++"

Edit:

Apparently also in "More Effective C++"
Item 11: Prevent exceptions from leaving destructors

查看更多
素衣白纱
5楼-- · 2018-12-31 04:58

Q: So my question is this - if throwing from a destructor results in undefined behavior, how do you handle errors that occur during a destructor?

A: There are several options:

  1. Let the exceptions flow out of your destructor, regardless of what's going on elsewhere. And in doing so be aware (or even fearful) that std::terminate may follow.

  2. Never let exception flow out of your destructor. May be write to a log, some big red bad text if you can.

  3. my fave : If std::uncaught_exception returns false, let you exceptions flow out. If it returns true, then fall back to the logging approach.

But is it good to throw in d'tors?

I agree with most of the above that throwing is best avoided in destructor, where it can be. But sometimes you're best off accepting it can happen, and handle it well. I'd choose 3 above.

There are a few odd cases where its actually a great idea to throw from a destructor. Like the "must check" error code. This is a value type which is returned from a function. If the caller reads/checks the contained error code, the returned value destructs silently. But, if the returned error code has not been read by the time the return values goes out of scope, it will throw some exception, from its destructor.

查看更多
登录 后发表回答