How to detect stack unwinding in a destructor
Asked Answered
P

3

7

I have a simple C++ object that I create at the start of function F() to ensure two matched functions (OpDo, OpUndo) are called at the start and return of the F(), by using the object's constructor and destructor. However, I don't want the operation to be undone in case an exception was thrown within the body of F(). Is this possible to do cleanly? I have read about std::uncaught_exception, but its use does not seem to be recommended.

Phyllys answered 25/10, 2010 at 20:24 Comment(3)
Using ´uncaught_exception´ might be ok, but it depends on what you're doing (and is thus subjective..). Nevertheless, could you elaborate on the do/undo operation and the exception(s) a bit more?Vaasta
If you don't want an action to happen when an exception is propogating then just don;t use RAII. Just put the OpDo() and OpUndo() directly into the code.Polyphemus
"its use does not seem to be recommended" - because it doesn't work as a means of detecting whether the bit of stack containing your object is being unwound because of an exception or because of a normal return. Suppose that someone puts some code in a destructor that calls your function F. Suppose further that such an object is destroyed as part of stack unwinding during an exception. Then uncaught_exception will return true, but no exception has been thrown within the body of F().Aerospace
S
6

Most people have used std::uncaught_exception() to try to tell if an exception is pending, so they can throw an exception from a destructor if there isn't one already. That is generally considered Not A Good Idea.

If you want to not undo an operation if an exception has thrown, it should do the trick.

Remember that the destructor is your last chance to release any resources an object has, because after the destructor ends the object does not exist, and any resources it held are now permanently leaked. If OpDo() allocates any memory or file handles or whatever, you do need to deal with that in the destructor no matter what.

Stane answered 25/10, 2010 at 20:31 Comment(0)
G
1

You can subvert the Scope Guard idiom. Instead of not doing something in the destructor when no exception is thrown, we invert that and only do something if no exception is thrown:

class DoUndoRAII{
public:
  DoUndoRAII()
    : noexcept_(false)
  {
    // your stuff here
  }

  ~DoUndoRAII(){
    if(noexcept_){
      // do whatever you need to do
    }
  }

  void no_exception(){
    noexcept_ = true;
  }

private:
  bool noexcept_;
};

void func(){
  DoUndoRAII do_undo;

  // last line
  do_undo.no_exception();
}

When an exception is thrown, do_undo.no_exception() will never be called and thus never set the noexcept_ value to true. :) An example can be found here on Ideone.

Gripper answered 17/4, 2011 at 8:8 Comment(0)
R
0

Let's assume that your F returns some class Helper:

Helper F()
{
     MyClass doUndoWrapper;
}

When flow is normal - Helper is created. When exception is raised no copy of Helper is created. Try use this semantic by placing to private region constructor of Helper and declaring F as friend - so no one could create helper.

class Helper
{
private:
    friend Helper F();
    Helper(){ //place there OpDo semantic - first entry 
              // construct this class
    Helper(const Helper& copy){ //this must present to allow stack operations
              // copy constructor will be called at end of `F` to return value
              // so place OpUndo semantic there to mark success without exception
Repertoire answered 25/10, 2010 at 20:40 Comment(1)
@C Johnson - If you are using Helper as return value you get follow benefits (1) Helper is created at at the beginning as required (so place OpDo to constructor) (2) You can recognize if function F has returned successfully - the copy constructor called (so place OpUndo there) (3) in case of exception no copy constructor called. To clarify this, I'm changing code to reflect this idea.Repertoire

© 2022 - 2024 — McMap. All rights reserved.