C++ doesn't provide a
There are numerous Scope Guard or Scope Exit constructs around, including one in the Boost library and the D Language's scope(exit) construct, that aim to tidy up this pattern, making it quicker to write and easier to read. My good friend Steve recently sent me his version of this idea - it was characteristically brilliant and I decided to have a go myself.
Using the example of a wrapper for
What can be done about the wordiness though? Any syntactic sweetener for this pattern will have to simplify usage, obviously, or what's the point? Well, thanks to some of the new features introduced with C++ 11 we can wrap the whole affair up nicely in a macro that's simple to write and clear when read. Let's build it up bit by bit.
The main thing we want to achieve is ease of use, and hence readability (and then onto reliability) - this means keeping the number of parameters to a minimum. So what is the minimum? Arbitrary cleanup code could involve an arbitrary number of parameters and so to avoid hand-cranking each and every reference we'd employ a lambda, closing around any variables we need. This neatly brings the maximum number of parameters to our macro down to one - the body of that lambda.
We have no way of knowing, up front, what the type of such a construct will be called, and so we turn to the tools of type inference,
finally
clause for structured exception handling and it doesn't need to. The deterministic object destruction of C++ provides the perfect way to ensure that your cleanup code is executed - tying your resource management to an object's lifetime ensures that it will take place when the memory that object occupies is reclaimed; for local objects this corresponds to control hitting the end of the lexical scope in which the object is declared, whether that scope is left via normal flow or due to an exception.There are numerous Scope Guard or Scope Exit constructs around, including one in the Boost library and the D Language's scope(exit) construct, that aim to tidy up this pattern, making it quicker to write and easier to read. My good friend Steve recently sent me his version of this idea - it was characteristically brilliant and I decided to have a go myself.
Using the example of a wrapper for
FormatMessage
(a Windows function which I personally can never remember how to call without consulting documention), we can see how RAII can be gainfully employed. The flags I'm passing in tell FormatMessage
to allocate a buffer big enough for the data returned, that allocation is done using LocalAlloc
and it's up to us to clean up by using LocalFree
. Here's how you might go about this (console output added for illustration)
#include <tchar.h>
#include <string>
typedef std::basic_string<TCHAR> tstring;
namespace manual
{
tstring MessageFromHR( HRESULT hr )
{
LPTSTR ptr = NULL;
struct cleanup {
LPTSTR& p;
~cleanup() {
cout << "about to free pointer 0x" << hex << (int)p << endl;
LocalFree( p );
cout << "done" << endl;
}
} cu = { ptr };
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
, NULL
, hr
, MAKELANGID( LANG_NEUTRAL, SUBLANG_NEUTRAL )
, (LPTSTR)&ptr
, 0
, NULL
);
return tstring( ptr );
}
}
This approach is very wordy but it does nicely put the cleanup code right next to the declaration of the variable that represents the leak-prone resource. This lexical locality has a huge effect on readability and hence ultimately on reliability.
What can be done about the wordiness though? Any syntactic sweetener for this pattern will have to simplify usage, obviously, or what's the point? Well, thanks to some of the new features introduced with C++ 11 we can wrap the whole affair up nicely in a macro that's simple to write and clear when read. Let's build it up bit by bit.
The main thing we want to achieve is ease of use, and hence readability (and then onto reliability) - this means keeping the number of parameters to a minimum. So what is the minimum? Arbitrary cleanup code could involve an arbitrary number of parameters and so to avoid hand-cranking each and every reference we'd employ a lambda, closing around any variables we need. This neatly brings the maximum number of parameters to our macro down to one - the body of that lambda.
We have no way of knowing, up front, what the type of such a construct will be called, and so we turn to the tools of type inference,
auto
and decltype
. These keywords allow us to declare a variable of fixed but unknown type and, crucially, refer to that type later on
#include <tchar.h>
#include <string>
typedef std::basic_string<TCHAR> tstring;
namespace lambda
{
tstring MessageFromHR( HRESULT hr )
{
LPTSTR ptr = NULL;
auto __l = [&]() {
cout << "about to free pointer 0x" << hex << (int)ptr << endl;
LocalFree( ptr );
cout << "done" << endl;
};
struct cleanup {
decltype( __l )& l;
~cleanup() { l(); }
} __i = { __l };
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
, NULL
, hr
, MAKELANGID( LANG_NEUTRAL, SUBLANG_NEUTRAL )
, (LPTSTR)&ptr
, 0
, NULL
);
return tstring( ptr );
}
}
Turning it into a macro is pretty simple
#define SIMPLE_ENSURE( routine ) auto __l = [&]() { routine; };\
struct __s {\
decltype( __l )& l;\
~__s() { l(); }\
} __i = { __l };
But we're not quite done here - given that we're defining named types and instances we'll be limited to one per lexical scope. We can fix this by using the __LINE__
macro to generate unique names. Note the ID macro and inner implementation ENSURE_
- this is necessary due to the way macro expansion works. Note also that the instance variable r
in the struct is a reference to the lambda.
#define ID( base, unique ) base##unique
#define ENSURE_( routine, unique ) auto ID( r, unique ) = [&]() { routine; };\
struct ID( e, unique ) {\
decltype( ID( r, unique ) )& r;\
ID( ~e, unique )() { r(); }\
} ID( ei, unique ) = { ID( r, unique ) };
#define ENSURE( routine ) ENSURE_( routine, __LINE__ )
Here's the FormatMessage
wrapper again, this time using the ENSURE
macro
#include <tchar.h>
#include <string>
typedef std::basic_string<TCHAR> tstring;
namespace macro
{
tstring MessageFromHR( HRESULT hr )
{
LPTSTR ptr = NULL;
ENSURE(
cout << "about to free pointer 0x" << hex << (int)ptr << endl;
LocalFree( ptr );
cout << "done" << endl;
)
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
, NULL
, hr
, MAKELANGID( LANG_NEUTRAL, SUBLANG_NEUTRAL )
, (LPTSTR)&ptr
, 0
, NULL
);
return tstring( ptr );
}
}
All these examples yield the same output (pointers may vary, but notice it's no longer NULL
by the time it's freed - that's because the lambda captures by reference)
about to free pointer 0x4888e0
done
And all return the correct message
No such interface supported
And here's an example of multiple instances in the same scope
int main( int argc, const char* argv[] )
{
try
{
cout << "one" << endl;
ENSURE( cout << "four" << endl )
ENSURE( cout << "three" << endl )
cout << "two" << endl;
throw std::exception();
}
catch ( const std::exception& e )
{
cout << "five" << endl;
}
}
This illustrates how the execution of ENSURE
blocks is deferred until the end of the current scope and also the fact that they run in reverse order, as per normal object destruction.
And here's what the preprocessor chucks out (line breaks added)
int main( int argc, const char* argv[] )
{
try
{
cout << "one" << endl;
auto r141 = [&]() { cout << "four" << endl; }; struct e141
{ decltype( r141 )& r; ~e141() { r(); } } ei141 = { r141 };
auto r142 = [&]() { cout << "three" << endl; }; struct e142
{ decltype( r142 )& r; ~e142() { r(); } } ei142 = { r142 };
cout << "two" << endl;
throw std::exception();
}
catch ( const std::exception& e )
{
cout << "five" << endl;
}
}
Of course in production code you wouldn't have all the console output and it would look like this
#include <tchar.h>
#include <string>
typedef std::basic_string<TCHAR> tstring;
namespace production
{
tstring MessageFromHR( HRESULT hr )
{
LPTSTR ptr = NULL;
ENSURE( LocalFree( ptr ) )
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
, NULL
, hr
, MAKELANGID( LANG_NEUTRAL, SUBLANG_NEUTRAL )
, (LPTSTR)&ptr
, 0
, NULL
);
return tstring( ptr );
}
}
I should mention that this sort of in-line cleanup code is not always appropriate - sometimes what you need is a proper class, defined in its own file, that wraps up a resource and manages its life time. This gives you testability and separation of concerns. However, as with all things, it's a trade off and I feel that the lexical locality wins out in the case of that one little call to LocalFree
.