Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Generic: Change the Way You Write Exception-Safe Code — Forever


Solution 4: Petru's Approach

Using the ScopeGuard tool (which we'll explain in a minute), you can easily write code that's simple, correct, and efficient:

void User::AddFriend(User& newFriend)
{
    friends_.push_back(&newFriend);
    ScopeGuard guard = MakeObjGuard(
        friends_, &UserCont::pop_back);
    pDB_->AddFriend(GetName(), newFriend.GetName());
    guard.Dismiss();
}

guard's only job is to call friends_.pop_back when it exits its scope. That is, unless you Dismiss it. If you do that, guard no longer does anything. ScopeGuard implements automatic calls to functions or member functions in its destructor. It can be helpful when you want to implement automatic undoing of atomic operations in the presence of exceptions.

You use ScopeGuard like so: if you need to do several operations in an "all-or-none" fashion, you put a ScopeGuard after each operation. The execution of that ScopeGuard nullifies the effect of the operation above it:

friends_.push_back(&newFriend);
ScopeGuard guard = MakeObjGuard(
    friends_, &UserCont::pop_back);
ScopeGuard works with regular functions, too:
void* buffer = std::malloc(1024);
ScopeGuard freeIt = MakeGuard(std::free, buffer);
FILE* topSecret = std::fopen("cia.txt");
ScopeGuard closeIt = MakeGuard(std::fclose, topSecret);

If all atomic operations succeed, you Dismiss all guards. Otherwise, each constructed ScopeGuard will diligently call the function with which you initialized it.

With ScopeGuard you can easily arrange to undo various operations without having to write special classes for removing the last element of a vector, freeing some memory, and closing a file. This makes ScopeGuard a very useful reusable solution for writing exception-safe code, easily.

Implementing ScopeGuard

ScopeGuard is a generalization of a typical implementation of the "initialization is resource acquisition" C++ idiom. The difference is that ScopeGuard focuses only on the cleanup part — you do the resource acquisition, and ScopeGuard takes care of relinquishing the resource. (In fact, cleaning up is arguably the most important part of the idiom.)

There are different ways of cleaning up resources, such as calling a function, calling a functor, and calling a member function of an object. Each of these can require zero, one, or more arguments.

Naturally, we model these variations by building a class hierarchy. The destructors of the objects in the hierarchies do the actual work. The base of the hierarchy is the ScopeGuardImplBase class, shown below:

class ScopeGuardImplBase
{
public:
    void Dismiss() const throw()
    {    dismissed_ = true;    }
protected:
    ScopeGuardImplBase() : dismissed_(false)
    {}
    ScopeGuardImplBase(const ScopeGuardImplBase& other)
    : dismissed_(other.dismissed_)
    {    other.Dismiss();    }
    ~ScopeGuardImplBase() {} // nonvirtual (see below why)
    mutable bool dismissed_;

private:
    // Disable assignment
    ScopeGuardImplBase& operator=(
        const ScopeGuardImplBase&);
};

ScopeGuardImplBase manages the dismissed_ flag, which controls whether derived classes perform cleanup or not. If dismissed_ is true, then derived classes will not do anything during their destruction.

This brings us to the missing virtual in the definition of ScopeGuardImplBase's destructor. What polymorphic behavior of the destructor would you expect if it's not virtual? Hold your curiosity for a second; we have an ace up our sleeves that allows us to obtain polymorphic behavior without the overhead of virtual functions

For now, let's see how to implement an object that calls a function or functor taking one argument in its destructor. However, if you call Dismiss, the function/functor is no longer invoked.

template <typename Fun, typename Parm>
class ScopeGuardImpl1 : public ScopeGuardImplBase
{
public:
    ScopeGuardImpl1(const Fun& fun, const Parm& parm)
    : fun_(fun), parm_(parm) 
    {}
    ~ScopeGuardImpl1()
    {
        if (!dismissed_) fun_(parm_);
    }
private:
    Fun fun_;
    const Parm parm_;
};
To make it easy to use ScopeGuardImpl1, let's write a helper function.
template <typename Fun, typename Parm>
ScopeGuardImpl1<Fun, Parm>
MakeGuard(const Fun& fun, const Parm& parm)
{
    return ScopeGuardImpl1<Fun, Parm>(fun, parm);
}

MakeGuard relies on the compiler's ability to deduce template arguments for template functions. This way you don't need to specify the template arguments to ScopeGuardImpl1 — actually, you don't need to explicitly create ScopeGuardImpl1 objects. This trick is used by standard library functions, such as make_pair and bind1st.

Still curious about how to achieve polymorphic behavior of the destructor without a virtual destructor? It's time to write the definition of ScopeGuard, which, surprisingly, is a mere typedef:

typedef const ScopeGuardImplBase& ScopeGuard;

Now we'll disclose the whole mechanism. According to the C++ Standard, a reference initialized with a temporary value makes that temporary value live for the lifetime of the reference itself.

Let's explain this with an example. If you write:

FILE* topSecret = std::fopen("cia.txt");
ScopeGuard closeIt = MakeGuard(std::fclose, topSecret);

then MakeGuard creates a temporary variable of type (deep breath here):

ScopeGuardImpl1<int (&)(FILE*), FILE*>

This is because the type of std::fclose is a function taking a FILE* and returning an int. The temporary variable of the type above is assigned to the const reference closeIt. As stated in the language rule above, the temporary variable lives as long as the reference — and when it is destroyed, the correct destructor is called. In turn, the destructor closes the file. ScopeGuardImpl1 supports functions (or functors) taking one parameter. It is very simple to build classes that accept zero, two, or more parameters (ScopeGuardImpl0, ScopeGuardImpl2...). Once you have these, you overload MakeGuard to achieve a nice, unified syntax:

template <typename Fun>
ScopeGuardImpl0<Fun>
MakeGuard(const Fun& fun)
{
    return ScopeGuardImpl0<Fun >(fun);
}
...

We already have a powerful means of expressing automatic calls to functions. MakeGuard is an excellent tool especially when it comes to interfacing with C APIs without having to write lots of wrapper classes.

What's even better is the preservation of efficiency, as there's no virtual call involved.

ScopeGuard for Objects and Member Functions

So far, so good, but what about invoking member functions for objects? It's not hard at all. Let's implement ObjScopeGuardImpl0, a class template that can invoke a parameterless member function for an object.

template <class Obj, typename MemFun>
class ObjScopeGuardImpl0 : public ScopeGuardImplBase
{
public:
    ObjScopeGuardImpl0(Obj& obj, MemFun memFun)
    : obj_(obj), memFun_(memFun) 
    {}
    ~ObjScopeGuardImpl0()
    {
        if (!dismissed_) (obj_.*fun_)();
    }
private:
    Obj& obj_;
    MemFun memFun_;
};

ObjScopeGuardImpl0 is a bit more exotic because it uses the lesser-known pointers to member functions and operator.*. To understand how it works, let's take a look at MakeObjGuard's implementation. (We availed ourselves of MakeObjGuard in the opening section.)

template <class Obj, typename MemFun>
ObjScopeGuardImpl0<Obj, MemFun, Parm>
MakeObjGuard(Obj& obj, Fun fun)
{
    return ObjScopeGuardImpl0<Obj, MemFun>(obj, fun);
}

Now if you call:

ScopeGuard guard = MakeObjGuard(
    friends_, &UserCont::pop_back);

then an object of the following type is created:

ObjScopeGuardImpl0<UserCont, void (UserCont::*)()>

Fortunately, MakeObjGuard saves you from having to write types that look like uninspired emoticons. The mechanism is the same — when guard leaves its scope, the destructor of the temporary object is called. The destructor invokes the member function via a pointer to a member. To achieve that, we use operator.*.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.