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?