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?
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