c++exception

Exception safety - patterns for reliably rolling back object state


I've been thinking about how to implement the various exception safety guarantee, especially the the strong guarantee, i.e. data is rolled back to it's original state when an exception occurs.

Consider the following, wonderfully contrived examples (C++11 code). Suppose there is a simple data structure storing some value

struct Data
{
  int value = 321;
};

and some function modify() operating on that value

void modify(Data& data, int newValue, bool throwExc = false)
{
  data.value = newValue;

  if(throwExc)
  {
    // some exception occurs, sentry will roll-back stuff
    throw std::exception();
  }
}

(one can see how contrived this is). Suppose we wanted to offer the strong exception-safety guarantee for modify(). In case of an exception, the value of Data::value is obviously not rolled back to its original value. One could naively go ahead and try the whole function, setting back stuff manually in appropriate catch block, which is enormously tedious and doesn't scale at all.

Another approach is to use some scoped, RAII helper - sort of like a sentry which knows what to temporarily save and restore in case of an error:

struct FakeSentry
{
  FakeSentry(Data& data) : data_(data), value_(data_.value)
  {
  }

  ~FakeSentry()
  {
    if(!accepted_)
    {
      // roll-back if accept() wasn't called
      data_.value = value_;
    }
  }

  void accept()
  {
    accepted_ = true;
  }

  Data& data_ ;
  int   value_;

  bool accepted_ = false;
};

The application is simple and require to only call accept() in case of modify() succeeding:

void modify(Data& data, int newValue, bool throwExc = false)
{
  FakeSentry sentry(data);
  data.value = newValue;

  if(throwExc)
  {
    // some exception occurs, sentry will roll-back stuff
    throw std::exception();
  }

  // prevent rollback
  sentry.accept();
}

This gets the job done but doesn't scale well either. There would need to be a sentry for each distinct user-defined type, knowing all the internals of said type.

My question now is: What other patterns, idioms or preferred courses of action come to mind when trying to implement strongly exception safe code?


Solution

  • In general it is called ScopeGuard idiom. It is not always possible to use temporary variable and swap to commit (though it is easy when acceptable) - sometime you need to modify existing structures.

    Andrei Alexandrescu and Petru Marginean discuss it in details in following paper: "Generic: Change the Way You Write Exception-Safe Code — Forever".


    There is Boost.ScopeExit library which allows to make guard code without coding auxiliary classes. Example from documentation:

    void world::add_person(person const& a_person) {
        bool commit = false;
    
        persons_.push_back(a_person);           // (1) direct action
        // Following block is executed when the enclosing scope exits.
        BOOST_SCOPE_EXIT(&commit, &persons_) {
            if(!commit) persons_.pop_back();    // (2) rollback action
        } BOOST_SCOPE_EXIT_END
    
        // ...                                  // (3) other operations
    
        commit = true;                          // (4) disable rollback actions
    }
    

    D programming language has special construct in language for that purpose - scope(failure)

    Transaction abc()
    {
        Foo f;
        Bar b;
    
        f = dofoo();
        scope(failure) dofoo_undo(f);
    
        b = dobar();
    
        return Transaction(f, b);
    }:
    

    Andrei Alexandrescu shows advantages of that language construct in his talk: "Three Unlikely Successful Features of D"


    I have made platform dependent implementation of scope(failure) feature which works on MSVC, GCC, Clag and Intel compilers. It is in library: stack_unwinding. In C++11 it allows to achieve syntax which is very close to D language. Here is Online DEMO:

    int main()
    {
        using namespace std;
        {
            cout << "success case:" << endl;
            scope(exit)
            {
                cout << "exit" << endl;
            };
            scope(success)
            {
                cout << "success" << endl;
            };
            scope(failure)
            {
                cout << "failure" << endl;
            };
        }
        cout << string(16,'_') << endl;
        try
        {
            cout << "failure case:" << endl;
            scope(exit)
            {
                cout << "exit" << endl;
            };
            scope(success)
            {
                cout << "success" << endl;
            };
            scope(failure)
            {
                cout << "failure" << endl;
            };
            throw 1;
        }
        catch(int){}
    }
    

    Output is:

    success case:
    success
    exit
    ________________
    failure case:
    failure
    exit