C ++ using RAII with a destructor that throws

Say I have a RAII class:

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

And if I have a function:

 void foo() { Raii raii; if (something) { throw std::exception(); } } 

This is bad, because during the cleanup for the first exception, we can reset again, and this will complete the process.

My question is: what is a good pattern for using raii for code that cleanup can expose?

For example, is it good or bad - why?

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

Note that a Raii object is always an object allocated by the stack, and this is not a general throw from the destructor problem.

+6
source share
2 answers

C ++ will almost certainly have a function to get the current number of exceptions from C ++ 1z (aka C ++ 17 if they publish it on time!): std::uncaught_exceptions (note the plural "s"). In addition, destructors are declared as noexcept by default (this means that if you try to exit the destructor with an exception, std::terminate is called).

So, firstly, mark your destructor as throwing ( noexcept(false) ). Then keep track of the number of active exceptions in ctor, compare them with the value in dtor: if there are more exceptions in dtor, you know that you are currently in the process of expanding the stack, and pressing again will result in a call to std::terminate .

Now you precisely determine how exceptional you are and how you want to deal with the situation: exit the program or just swallow an internal exception?

A bad simulation is not thrown if uncaught_exception (only) returns true, but this makes the exceptions inoperative when called from another dtor caused by an unwrapping that tries to catch and handle your exception. This option is available in current C ++ standards.

+7
source

Advice article scopeguard was

In the area of ​​exceptions, it’s crucial that you can’t do anything if your undo / redo action is not performed. You are trying to perform a cancel operation, and you are moving whether the cancel operation is successful or not.

It may seem crazy, but think:

  • I manage to run out of memory and get a std::bad_alloc
  • My cleanup code logs an error
  • Unfortunately, the recording failed (the disk may be full) and is trying to throw an exception

Can I cancel a journal entry? Should I try?

When an exception is thrown, all you really know is that the program is in an invalid state. You should not be surprised that some impossible things are possible in the end. Personally, I have seen many more cases where Alexandrescu's advice makes the most sense than otherwise: try to clean it up, but admit that the first exception means that things are already in an unacceptable state, so additional crashes - especially crashes caused by the first problem (" cascade of errors ") - should not be a surprise. And trying to cope with them will fail. [/ P>


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

When Capn Proto code can throw an exception from the destructor, it first checks std::uncaught_exception() to make sure it is safe. If another exception is already active, the new exception is considered a side effect of the main exception and is either tacitly swallowed or reported on the side channel.

But, as Jakk said, destructors became nothrow(true) by default in C ++ 11. This means that if you want to do this, you must be sure that in C ++ 11 and later you mark the destructor as nothrow(false) . Otherwise, an exception from the destructor excludes the program, even if there is no other exception in flight. And note: "If another exception is already active, the new exception is considered a side effect of the main exception and is either tacitly swallowed or reported on the side channel."

+4
source

All Articles