Note that scope_guard
depends on std::tr1::bind
or boost::bind
!
Introduction
You've definitely heard of RAII - Resource Acquisition Is Initialization. This idiom simply says that you acquire/initialize a resource and free it after you are done using it.
With C++ classes the cleanup is normally done automatically for you in the destructor of the class. But C++ life is not always that simple - you might deal with C-APIs that return a raw pointer to a string or a file. Or you might have a more complex situation where you have to insert a user object into a vector in memory and in a database. If one of these operations fails you still want to have a consistent state.
This is not a new discovery but: are you really sure that the resource handles triggers in _every_ situation, i.e. not only in case of successful execution of a method but also in an error situation when you prematurely return from the method or errors are thrown? Cleanup can get really tedious and the code difficult to read and understand.
Background
First, I used JaeWook Choi's auto mate (am::mate
) [1] which I found very useful and powerful (It has e.g. conditional resource cleanup).
Then, I discovered the "scope guard", originally developed by Andrei Alexandrescu and Petru Marginean, that takes care of the cleanup part after you acquired a resource [2]. Be sure to read Alexandrescu's article and have a look at the loki library [3]. Read the improvement of Joshua Lehrer, too [4] - in this article I won't discuss the details of the scope guard but will show a different and lighter implementation.
Design issues
Stack allocation
The auto mate has one little disadvantage: It stores a copy of your cleanup functor
on the heap. This means that the auto mate has to allocate memory and this *could* fail and is not as fast as storing the functor
on the stack.
The scope guard stores your cleanup functor
on the stack (see note concerning the creation of scope guards).
Delegating functor complexity
Dealing with functor
s is not an easy task. There are plenty of forms: function objects, static
functions, member functions. They have different arities (the number of arguments), static
and member functions can have different calling conventions (__cdecl
, __stdcall
, __fastcall
), member functions can be const-volatile qualified. This variety is basically not a problem for the scope guard regarding function objects and static functions because it just expects a functor
with a certain arity, stores the functor
and binds its arguments. Things get more complicated with member functions because the scope guard also needs the object to call the member function. Thus it stores the object, the member function and its arguments.
With Loki, you can easily create a scope guard with the helper function MakeGuard
that is overloaded for many cases:
using namespace Loki;
ScopeGuard g1 = MakeGuard(&fclose, hFile);
ScopeGuard g2 = MakeGuard(&Object::do_sg, obj);
But it doesn't cover every possible situation, e.g. it doesn't provide an overload for cv-qualified member functions or those with different calling conventions.
With the arrival of the technical release 1 (tr1) we can expect that there will be powerful functor
adapters available in the stl, based on boost::bind
.
Separating scope guard functionality from functors
Anyway, I think that the purpose of a scope guard is to trigger a (nullary) functor when the guard goes out of scope (or not if the guard is dismissed). Its responsibility is not to deal with the functor
's arity - i.e. to serve as a binder for the functor
's parameters - nor to distinguish between any functor
and a member function, nor to deal with any qualifiers or calling conventions.
This effectively means that we can separate the real purpose of the scope guard from the creation of the functor
, thus making the implementation of the scope guard a lot simpler, easier to maintain and even more flexible. The creation of the functor
is then basically the programmer's responsibility. There are a lot of possibilities to create nullary functors from n-ary functions... just to name a few: stl (std::bind1st
, ...), sigc++ (sigc::bind
) or std::tr1::bind
(which is based on boost::bind
).
This gives the programmer the power of those binding facilities.
More restrictive class design
One existing restriction is that the scope guard class isn't assignable.
There should be further restrictions: To force the creation of the scope guard on the stack it must not be creatable on the heap with new
.
To restrict its usage entirely to the stack it shouldn't even be possible to make a pointer alias.
My approach
Note that the scope guard facilities except for the macros reside in the namespace
"sg"
.
The scope_guard_base
is the same as Loki::ScopeGuardImplBase
plus the restrictions discussed in the previous chapter.
The restrictions work as follows:
using namespace sg;
void do_sg()
{}
typedef scope_guard0<void (*)()> do_sg_resource_guard;
do_sg_resource_guard g = make_guard(&do_sg);
g = make_guard(&do_sg);
scope_guard g1 = ...;
const scope_guard_base* pg1 = &g1;
scope_guard_base* pg = new do_sg_resource_guard(&do_sg);
Here is the base class
:
class scope_guard_base
{
scope_guard_base& operator =(const scope_guard_base&);
void* operator new(std::size_t);
scope_guard_base* operator &();
const scope_guard_base* operator &() const;
protected:
scope_guard_base() throw()
: m_dismissed()
{}
~scope_guard_base()
{}
scope_guard_base(const scope_guard_base& other) throw()
: m_dismissed(other.m_dismissed)
{
other.dismiss();
}
template<typename T_janitor>
static void safe_execute(T_janitor& j) throw()
{
if (!j.m_dismissed)
{
try { j.execute(); }
catch(...)
{}
}
}
public:
void dismiss() const throw()
{
m_dismissed = true;
}
private:
mutable bool m_dismissed;
};
One scope guard for nullary functors only
I provide only one scope guard: a scope_guard0
(derived from scope_guard_base
) that takes a nullary functor
(functor
with no arguments):
template<typename T_functor>
class scope_guard0: public scope_guard_base
{
public:
explicit scope_guard0(T_functor functor)
: m_functor(functor)
{}
~scope_guard0() throw()
{
scope_guard_base::safe_execute(*this);
}
void execute()
{
m_functor();
}
protected:
T_functor m_functor;
};
Scope guard creation with make_guard()
There is only one helper function make_guard()
to easily create the above scope_guard
:
template<typename T_functor>
inline scope_guard0<T_functor>
make_guard(T_functor functor)
{
return scope_guard0<T_functor>(functor);
}
That's all!
Readability issues and the MAKE_GUARD() macro
You might argue that the readability suffers a lot by my design decision to just have a scope guard for nullary functor
s. Did you write e.g. before:
using namespace Loki;
ScopeGuard g = MakeGuard(&Object::do_sg, obj);
you have to write now:
using namespace sg;
scope_guard g = make_guard(boost::bind(&Object::do_sg, &obj));
scope_guard g = make_guard(boost::bind(&Object::do_sg, boost::ref(obj)));
This doesn't seem to be very intuitive and I agree.
I thought a lot about this issue and tried to hook into the tr1/boost bind facilities.
My first attempt was to provide overloads for the make_guard()
function but this gets quickly complex because I have to know the bind functor
type that I have to provide to the scope_guard0
.
The solution I came up with extends the scope guard implementation by a macro that creates a nullary functor
using std::tr1::bind
or boost::bind
by passing on the functor
and its arguments with some preprocessor magic and makes a guard from that functor
:
#define MAKE_GUARD(fun_n_args_tuple)\
::sg::make_guard(::boost::bind fun_n_args_tuple)
scope_guard g = MAKE_GUARD((&Object::do_sg, &obj));
scope_guard g = MAKE_GUARD((&ReleaseDC, hdc, NULL));
Note the double parentheses for the MAKE_GUARD()
macro! The macro expects just ONE argument, i.e. a n-tuple consisting of the functor
and its arguments eventually, enclosed in parentheses.
Of course, you can easily forget the extra parentheses, but then the compiler will cry badly because the MAKE_GUARD
macro yields incorrect code.
Anonymous scope guards
Many times, you won't need a named scope guard variable because most of the time you won't dismiss the guard or don't need a variable name documenting what this special scope guard does.
ON_SCOPE_EXIT()
The macro ON_SCOPE_EXIT()
serves this purpose (like Loki's LOKI_ON_BLOCK_EXIT()
):
FILE* hFile = _tfopen(_T("c:\\a_file.txt"), _T("wb"));
ON_SCOPE_EXIT((&fclose, hFile));
Note the double parentheses! Because ON_SCOPE_EXIT()
uses MAKE_GUARD()
you must pass a n-tuple to ON_SCOPE_EXIT()
.
SOME_SCOPE_GUARD
If you don't want or can't rely on ON_SCOPE_EXIT()
that expands to std::tr1::bind
or boost::bind
but still want to have an anonymous scope guard variable, you can use the macro SOME_SCOPE_GUARD
:
SOME_SCOPE_GUARD = make_guard(std::cout << boost::lambda::constant
("do something") << '\n');
tr1 and boost
If your compiler already provides the facilities from the technical release 1 (tr1) then you can define a macro before including the scope guard:
#define SCOPE_GUARD_HAVE_TR1_BIND
#include "scope_guard.h"
... and the MAKE_GUARD()
macro will use std::tr1::bind()
, otherwise boost::bind()
Things to know
Creation of the scope guard could also fail (like the auto mate) if you bind e.g. a std::string
by value.
Every time the functor is copied the bound string is copied, too, and allocates memory for every copied string.
Loki's scope guard binds the functors parameters as const. As my implementation relies on other binding facilities you probably lose the const binding.
This is not a big problem in my opinion because you have to know how to use binding. But if you make a mistake the compiler won't tell you.
For example:
With the Loki scope guard the compiler will generate an error:
using namespace Loki;
void do_sg(int& x)
{
--x;
}
int i = 5;
ScopeGuard g = MakeGuard(&do_sg, i);
ScopeGuard g2 = MakeGuard(&do_sg, ByRef(i));
With my scope guard the compiler won't tell you:
using namespace sg;
void do_sg(int& x)
{
--x;
}
int i = 5;
scope_guard g1 = MAKE_GUARD((&do_sg, i));
scope_guard g2 = MAKE_GUARD((&do_sg, ref(i)));
If you have a member function for the guard you have to think about how you pass the object for the member function.
Loki's MakeGuard()
function and scope guard has reference semantics, i.e. they take this object by reference:
template<...>
ScopeGuard_N MakeGuard(T_return (T_obj1::*mem_fun)(), T_obj2& obj)
{}
ScopeGuard g = MakeGuard(&Obj::do_sg, obj);
whereas with my implementation you rely on the functor
library's capabilities.
std::tr1::bind
or boost::bind
have value semantics by default, i.e. the object is passed by value!
scope_guard g = MAKE_GUARD((&Obj::do_sg, obj));
scope_guard g = MAKE_GUARD((&Obj::do_sg, ref(obj)));
scope_guard g = MAKE_GUARD((&Obj::do_sg, &obj));
boost::bind
doesn't have bind()
overloads for volatile-qualified member functions
Note that std::tr1::bind
and boost::bind
can also take a shared_ptr!
class MyClass
{
public:
void do_sg()
{}
};
shared_ptr<MyClass> p;
ON_SCOPE_EXIT((&MyClass::do_sg, p));
If you want to use Windows API functions like ReleaseDC()
or CoTaskMemFree()
or COM methods be sure to define #define BOOST_BIND_ENABLE_STDCALL
when using boost (for tr1 I don't know by now).
References
- [1] JaeWook Choi: Writing an exception safe code in generic way
- [2] Andrei's and Petru's original article
- [3] Loki::ScopeGuard
- [4] Joshua Lehrer's description
History