Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ using RAII with destructor that throws

Let say I have RAII class:

class Raii {
    Raii() {};
    ~Raii() { 
        if (<something>) throw std::exception();
    }
};

And if I have the function:

void foo() {
    Raii raii;    

    if (something) {
       throw std::exception();
    }
} 

This is bad because while cleaning for the first exception we can throw again and this will terminate the process.

My question is - What is a good pattern to use raii for code that the cleanup might throw?

For example is this good or bad - why?

class Raii {
    Raii() {};
    ~Raii() {
        try {
           if (<something>) throw std::exception();
        }
        catch (...) {
           if (!std::uncaught_exception())
               throw;
        }
    }
};

Note that Raii object is always stack-allocated object - and this is not the general throw from destructor problem.

like image 689
gsf Avatar asked Apr 02 '16 01:04

gsf


People also ask

Should a destructor throw an exception?

The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped.

What is an advantage of using Raii over not using Raii?

Smart pointers use RAII to hide the manipulation of pointers, which are a lower level than business code, so RAII helps respect levels of abstraction in that case too. This is true for resource management in general, including database connection.

What happens if an exception is thrown from an object's constructor and object's destructor?

Constructors and destructorsEdit. When an exception is thrown from a constructor, the object is not considered instantiated, and therefore its destructor will not be called. But all destructors of already successfully constructed base and member objects of the same master object will be called.

What happens if destructor throws exception C++?

When an exception is thrown, destructors of the objects (whose scope ends with the try block) are automatically called before the catch block gets executed.


2 Answers

C++ will almost certainly have a function to obtain the current exception count as of C++1z (aka C++17 if they publish it on time!): std::uncaught_exceptions (note the plural "s"). Also, destructors are declared as noexcept by default (meaning that if you attempt to exit a destructor via an exception, std::terminate is called).

So, first, mark your destructor as throwing (noexcept(false)). Next, track the number of active exceptions in ctor, compare it to the value in dtor: if there are more uncaught exceptions in the dtor, you know that you are currently in the process of stack unwinding, and throwing again will result in a call to std::terminate.

Now you decide exactly how exceptional you really are and how you wish to handle the situation: terminate the program, or just swallow the inner exception?

A poor imitation is to not throw if uncaught_exception (singular) returns true, but that makes the exceptions not work when called from a different dtor's triggered by unrolling that is trying to catch and process your exception. This option is available in current C++ standards.

like image 178
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 06:10

Yakk - Adam Nevraumont


The advice from the ScopeGuard article was

In the realm of exceptions, it is fundamental that you can do nothing if your "undo/recover" action fails. You attempt an undo operation, and you move on regardless whether the undo operation succeeds or not.

It may sound crazy, but consider:

  1. I manage to run out of memory and get a std::bad_alloc exception
  2. My clean-up code logs the error
  3. Unfortunately, the write fails (maybe the disk is full), and tries to throw an exception

Can I undo the log write? Should I try?

When an exception is thrown, all you really know is that the program is in an invalid state. You shouldn't be surprised that some impossible things turn out to be possible after all. Personally, I've seen far more cases where Alexandrescu's advice makes the most sense than otherwise: try to clean up, but recognize that the first exception means that things are already in an invalid state, so additional failures -- especially failures caused by the first problem ("error cascade") -- should not be a surprise. And trying to handle them is not going to end well.


I should probably mention that Cap'n Proto does exactly what you've proposed:

When Cap’n Proto code might throw an exception from a destructor, it first checks std::uncaught_exception() to ensure that this is safe. If another exception is already active, the new exception is assumed to be a side-effect of the main exception, and is either silently swallowed or reported on a side channel.

But, as Yakk said, destructors became nothrow(true) by default in C++11. Which means that if you want to do this, you need to be sure that in C++11 and later you mark the destructor as nothrow(false). Otherwise, throwing an exception from the destructor will terminate the program even if there is no other exception in flight. And note, "If another exception is already active, the new exception is assumed to be a side-effect of the main exception, and is either silently swallowed or reported on a side channel."

like image 39
Max Lybbert Avatar answered Oct 19 '22 05:10

Max Lybbert