Click here to Skip to main content
15,881,559 members
Articles / Programming Languages / C++
Article

Automatic resource cleanup - a lightweight scope guard implementation

Rate me:
Please Sign up or sign in to vote.
4.93/5 (10 votes)
26 Apr 20077 min read 46.8K   321   30   6
An article on automatic resource cleanup for exception safe code, presenting a lightweight scope guard implementation

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 functors 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:

C++
using namespace Loki;

// guard for a static function
ScopeGuard g1 = MakeGuard(&fclose, hFile);
// guard for a member function
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:

C++
using namespace sg;

void do_sg()
{}

typedef scope_guard0<void (*)()> do_sg_resource_guard;

// can't (re)assign
do_sg_resource_guard g = make_guard(&do_sg);
g = make_guard(&do_sg);

// can't make a pointer alias
scope_guard g1 = ...;
const scope_guard_base* pg1 = &g1;

// can't create on the heap
scope_guard_base* pg = new do_sg_resource_guard(&do_sg);

Here is the base class:

C++
class scope_guard_base/*: nonassignable, nonheapcreatable, nonpointeraliasing*/
{
    /// Copy-assignment operator is not implemented and private.
    scope_guard_base& operator =(const scope_guard_base&);
    /// can't create on the heap
    void* operator new(std::size_t);
    /// can't pointer alias
    scope_guard_base* operator &();
    /// can't const pointer alias
    const scope_guard_base* operator &() const;

protected:
    scope_guard_base() throw()
        : m_dismissed()
    {}

    ~scope_guard_base()
    {}

    /// Copy-ctor takes over responsibility from other scope_guard.
    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):

C++
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:

C++
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 functors. Did you write e.g. before:

C++
using namespace Loki;
ScopeGuard g = MakeGuard(&Object::do_sg, obj);

you have to write now:

C++
using namespace sg;
scope_guard g = make_guard(boost::bind(&Object::do_sg, &obj));
// or even
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:

C++
#define MAKE_GUARD(fun_n_args_tuple)\
    ::sg::make_guard(::boost::bind fun_n_args_tuple)

// use it like this:
scope_guard g = MAKE_GUARD((&Object::do_sg, &obj));
// or
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()):

C++
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:

C++
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:

C++
#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:

    C++
    using namespace Loki;
    
    void do_sg(int& x)
    {
        --x;
    }
    
    int i = 5;
    // MakeGuard makes a ScopeGuard binding "i" as a const value.
    // Therefore the compiler will report an error because you can't
    // pass a const int to a int&
    ScopeGuard g = MakeGuard(&do_sg, i);
    
    // you must bind explicitly by reference:
    ScopeGuard g2 = MakeGuard(&do_sg, ByRef(i));
    // ... after the guard fires, "i" will be 4

    With my scope guard the compiler won't tell you:

    C++
    using namespace sg;
    
    void do_sg(int& x)
    {
        --x;
    }
    
    int i = 5;
    // std::tr1::bind or boost::bind will bind "i" as a not-const value.
    // Therefore the compiler won't report an error
    scope_guard g1 = MAKE_GUARD((&do_sg, i));
    // ... after the guard fires, "i" will still be 5
    
    // you must bind explicitly by reference (std::tr1::ref or boost::ref):
    scope_guard g2 = MAKE_GUARD((&do_sg, ref(i)));
    // ... after the guard fires, "i" will be 4
  • 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:

    C++
    template<...>
    ScopeGuard_N MakeGuard(T_return (T_obj1::*mem_fun)(), T_obj2& obj)
    {}
    
    // obj is passed by reference
    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!

    C++
    // passed by value!
    scope_guard g = MAKE_GUARD((&Obj::do_sg, obj));
    
    // pass explicitly by reference, tr1::ref() or boost::ref()
    scope_guard g = MAKE_GUARD((&Obj::do_sg, ref(obj)));
    // or use a pointer
    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!

    C++
    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

  • April 18th 2007
    • Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Austria Austria
I've always been a computer freak. In my childhood I enjoyed gaming on the C64 and Amiga500, later I started programming in various programming languages.

My technological passions at the moment include c++, xml/xslt and angular js.

For a living I work as a senior software developer.

Comments and Discussions

 
GeneralExample involving overloaded methods Pin
Rasqual Twilight27-May-09 13:35
Rasqual Twilight27-May-09 13:35 
GeneralRe: Example involving overloaded methods Pin
triendl.kj10-Jun-09 11:34
professionaltriendl.kj10-Jun-09 11:34 
GeneralMinor fix required for example code Pin
wtwhite24-Apr-07 23:08
wtwhite24-Apr-07 23:08 
Hi,

You describe a very useful solution to a common problem. As a minor quibble, I did notice that in the first example code panel under your "Things to Know" section, you have code that reads:

<br />
ScopeGuard g = MakeGuard(&x, i);<br />


and

<br />
ScopeGuard g2 = MakeGuard(&x, ByRef(i));<br />


I think these should read:

<br />
ScopeGuard g = MakeGuard(&do_sg, i);<br />


and

<br />
ScopeGuard g2 = MakeGuard(&do_sg, ByRef(i));<br />


respectively.

Thanks for an interesting article!
Tim
GeneralRe: Minor fix required for example code Pin
triendl.kj26-Apr-07 9:54
professionaltriendl.kj26-Apr-07 9:54 
GeneralRight on the spot! Pin
peterchen19-Apr-07 6:33
peterchen19-Apr-07 6:33 
GeneralRe: Right on the spot! Pin
triendl.kj23-Apr-07 11:05
professionaltriendl.kj23-Apr-07 11:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.