One of C++'s strengths is automatic code invocation at scope-exit (aka RAII). The traditional idea behind RAII of having specialized types for every resource type with a custom destructor that frees it is good in theory but in practice comes with a lot of friction and thus boiler-plate and frustration for the developer, that now has to create a new type for every little rollback operation he wants to perform. The problem is exacerbated with C++11 move operators: making a small "smart" class that does some form of cleanup now requires even more boilerplate.
Fortunately there's a way to neatly work around this, and the idea comes from D's scope
keyword or Go's defer
. We want a mechanism that tells the compiler to invoke a snippet of inlined code at scope exit. Something like
// Acquire a resource
Resource* resource = AcquireResource();
// Then whatever happens, release the resource when it goes out of scope.
finally {
ReleaseResource(resource);
};
// Safely do operations with the resource and early out if some error occurs. No need to worry about its cleanup.
More commonly, we stumble in situations where we want to write transaction like, i.e. perform a series of operations with side-effects and roll them all back in reverse order of execution if something goes wrong. Something like the following
// Returns 0 if succesful, a negative number if an error occurred.
int CalibrateUniverse()
{
// Holds the final result of the operation. Initialize it to failure.
int result = -1;
// Acquire a resource
Resource* resource = AcquireResource();
// If result evaluates to "true" at scope exit (i.e. an error occurred), release the resource.
rollback(result) {
ReleaseResource(resource);
};
// Now run some operation that may fail.
if (DoOperationWith(resource) < 0)
// It failed. Just return result, no need to manually figure out what to rollback
return result;
// ... other operations ...
// All went fine! Just set 0 zero to result and return it. This will disable all rollbacks in this scope.
return (result = 0);
}
The really neat thing about rollback
is that it code using it guarantees that you can return/break/goto/throw from any point in code and all the resources that need to be released, will be released appropriately. It alleviates the programmer from quite some mental burden and makes code much less error prone.
It happens that the previous constructs finally
and rollback
can be easily implemented exactly as they appear, using a combination of macro, lambda and operator overload.
Implementation of finally
#define finally\
auto __paste2(_finally_, __COUNTER__) = detail::make_finally & [&]
namespace detail
{
enum make_finally_t { make_finally };
template <typename Function>
struct finally_helper
{
Function m_function;
~finally_helper()
{
m_function();
}
};
template <typename Function>
finally_helper<Function> operator&(make_finally_t, Function function)
{
return{ std::forward<T>(function) };
}
}
Where _paste2
is somewhere defined to
#define _paste1(a, b) a##b
#define _paste2(a, b) _paste1(a, b)
And here is the rollback
implementation
#define rollback(result)\
auto _paste2(_rollback_, __COUNTER__) = detail::make_rollback<decltype(result)>{ result } & [&]
namespace detail
{
template <typename Flag>
struct make_rollback
{
Flag& flag;
};
template <typename Flag, typename Function>
struct rollback_helper
{
Flag& flag;
Function function;
~rollback_helper()
{
if (flag)
function();
}
};
template <typename Flag, typename Function>
rollback_helper<Flag, Function> operator&(make_rollback<Flag> make, Function function)
{
return{ make.flag, std::forward<Function>(function) };
}
}
Both GCC and Clang inline the lambda at each scope exit point with at least -O1
optimization level.