The final macro
The problem
Cleaning up resources is hard. One has to keep track of all allocated resources and make sure to free each one on each error path. The difficulty is compounded, some say, with exceptions, which can make error paths non-obvious. Traditionally, C++ programmers use RAII wrappers (e.g. unique_ptr
and CComPtr) to automatically clean up resources --- but it's inconvenient to create a new type for each possible cleanup activity. In exceptional code in particular, we're often forced to either create yet another RAII type, or duplicate cleanup code in the success and exception-rethrowing paths. It becomes messy quickly.
The solution
Other languages provide facilities (like finally and unwind-protect) to delay the execution of bits of code until the current task is completed; it's natural to clean up resources in finally blocks. C++ has never had finally blocks, but the latest language features allow us to create them ourselves. I've always said that the wondering thing about C++ is its ability to create very powerful, but also easy-to-use abstractions. It's powerful enough to implement features other languages have to have built in. Let's look at an example:
Code | Output |
|
|
The code above isn't witchcraft. It's standards-conforming† code. Here's the bit that makes it work:
#include <utility>
template<typename FunctorT>
struct FinallyUnwinder
{
FinallyUnwinder (FunctorT Functor)
: Functor (std::move (Functor))
{}
FinallyUnwinder (FinallyUnwinder&& Other)
: Functor (std::move (Other.Functor))
{}
~FinallyUnwinder ()
{ this->Functor (); }
private:
FinallyUnwinder (const FinallyUnwinder& other);
FinallyUnwinder& operator=(const FinallyUnwinder& other);
FunctorT Functor;
};
template<typename FunctorT>
typename FinallyUnwinder<FunctorT>
Finally (FunctorT Functor)
{
return FinallyUnwinder<FunctorT> (std::move (Functor));
}
#define UTX_CAT2(a,b) a ## b
#define UTX_CAT(a,b) UTX_CAT2 (a, b)
#define UTX_GENSYM(ar) UTX_CAT (ar, __LINE__)
#define FINALLY(...) \
auto UTX_GENSYM (utx_finally) = \
Finally ([&] () { __VA_ARGS__ ; });
This code relies on variadic macros, lambda functions, and move constructors, all of which are very recent features.
To see how it all fits together, let's look at the preprocessed version of one of the lines above:
auto utx_finally65 = Finally ([&] () { printf ("After throw\n") ; });;
Here, we're creating a lambda functor object, capturing all variables from the parent function by their addresses (which is fine, because this lambda will never run after its parent function returns), and stashing the result away in an automatic variable; its type is specific to the functor being assigned, and its name is created via macro magic. The compiler understands what's going on and can inline this entire process, producing code that's just as efficient as the hand-coded equivalent.
Conclusion
Using powerful primitives built into C++, we can create new expressive constructs, and we don't need to give up any performance in the process. The above macro, while tricky to implement, is an convenient alternative to explicit RAII. While previous attempts to enjoyed some success, it's only with C++11 that we're able to provide a natural and convenient syntax.
Notes
† - variadic macros are technically supported in C99, not C++, but support for variadic macros in C++11 compilers is universal.
Comments
Anonymous
July 20, 2011
Boost.ScopeExit does exactly that, so why should I implement this myself?Anonymous
July 20, 2011
The comment has been removedAnonymous
July 21, 2011
If you're using finally in C++, YOU ARE DOING IT WRONG.Anonymous
July 21, 2011
Andrei & Petru, December 2000: http://drdobbs.com/184403758Anonymous
July 21, 2011
This makes me die a little inside. It's like you don't know that C++ exceptions aren't for error handling. This language is not Java, sir.Anonymous
July 21, 2011
C++ exceptions are for error handling. That's the point.Anonymous
July 22, 2011
@John Haugeland, if exceptions aren't for error handling, then what do you recommend doing when a constructor fails ?Anonymous
July 23, 2011
The comment has been removedAnonymous
July 29, 2011
In D language there is greate way of doing cleanup using scope(exit/failure/success), blocks. It works regardles how you exit (return, jump, exception, error). Makes life so much easier.