Channels ▼
RSS

Database

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


Error Handling

If you have read Herb Sutter's work on exceptions [2], you know that it is essential that destructors must not throw an exception. A throwing destructor makes it impossible to write correct code and can shut down your application without any warning.

The destructors of ScopeGuardImplX and ObjScopeGuardImplX call an unknown function or member function respectively. These functions might throw. In theory, you should never pass functions that throw to MakeGuard or MakeObjGuard. In practice (as you can see in the downloadable code), the destructor is shielded from any exceptions:

template <class Obj, typename MemFun>
class ObjScopeGuardImpl0 : public ScopeGuardImplBase
{
    ...
public:
    ~ScopeGuardImpl1()
    {
        if (!dismissed_)
            try { (obj_.*fun_)(); }
            catch(...) {}
    }
}

The catch(...) block does nothing. This is not a hack. In the realm of exceptions, it is fundamental that you can do nothing if your "undo/recover" action fails. You attempt an undo operation, and you move on regardless whether the undo operation succeeds or not.

A possible sequence of actions in our instant messaging example is: you insert a friend in the database, you try to insert it in the friends_ vector and fail, and consequently you try to delete the user from the database. There is a small chance that somehow the deletion from the database fails, too, which leads to a very unpleasant state of affairs.

In general, you should put guards on operations that you are the most sure you can undo successfully.

Supporting Parameters by Reference

We were happily using ScopeGuard for a while, until we stumbled upon a problem. Consider the code below:

void Decrement(int& x) { --x; }
void UseResource(int refCount)
{
    ++refCount;
    ScopeGuard guard = MakeGuard(Decrement, refCount);
    ...
}

The guard object above ensures that the value of refCount is preserved upon exiting UseResource. (This idiom is useful in some resource sharing cases.)

In spite of its usefulness, the code above does not work. The problem is, ScopeGuard stores a copy of refCount (see the definition of ScopeGuardImpl1, member variable parm_) and not a reference to it. In this case, we need to store a reference to refCount so that Decrement can operate on it.

One solution would be to implement additional classes, such as ScopeGuardImplRef and MakeGuardRef. This is a lot of duplication, and it gets nasty as you implement classes for multiple parameters.

The solution we settled on consists of a little helper class that transforms a reference into a value:

template <class T>
class RefHolder
{
    T& ref_;
public:
    RefHolder(T& ref) : ref_(ref) {}
    operator T& () const
    {
        return ref_;
    }
};
template <class T>
inline RefHolder<T> ByRef(T& t)
{
    return RefHolder<T>(t);
}

RefHolder and its companion helper function ByRef are ingenious; they seamlessly adapt a reference to a value and allow ScopeGuardImpl1 to work with references without any modification. All you have to do is to wrap your references in calls to ByRef, like so:

void Decrement(int& x) { --x; }
void UseResource(int refCount)
{
    ++refCount;
    ScopeGuard guard = MakeGuard(Decrement, ByRef(refCount));
    ...
}

We find this solution to be pretty expressive and suggestive.

The nicest part of reference support is the const modifier used in ScopeGuardImpl1. Here's the relevant excerpt:

template <typename Fun, typename Parm>
class ScopeGuardImpl1 : public ScopeGuardImplBase
{
    ...
private:
    Fun fun_;
    const Parm parm_;
};

This little const is very important. It prevents code that uses non-const references from compiling and running incorrectly. In other words, if you forget to use ByRef with a function, the compiler will not allow incorrect code to compile.

But Wait, There's More

You now have everything you need to write exception-safe code without having to agonize over it. Sometimes, however, you want the ScopeGuard to always execute when you exit the block. In this case, creating a dummy variable of type ScopeGuard is awkward — you only need a temporary, you don't need a named temporary.

The macro ON_BLOCK_EXIT does exactly that and lets you write expressive code like below:

{
    FILE* topSecret = fopen("cia.txt");
    ON_BLOCK_EXIT(std::fclose, topSecret);
    ... use topSecret ...
} // topSecret automagically closed

ON_BLOCK_EXIT says: "I want this action to be performed when the current block exists." Similarly, ON_BLOCK_EXIT_OBJ implements the same feature for a member function call.

These macros use non-orthodox (albeit legal) macro wizardry, which shall go undisclosed. The curious can look them up in the code.

ScopeGuard in the Real World

Maybe the coolest thing about ScopeGuard is its ease of use and conceptual simplicity. This article has detailed the entire implementation, but explaining ScopeGuard's usage only takes a couple of minutes. Amongst our colleagues, ScopeGuard has spread like wildfire. Everybody takes ScopeGuard for granted as a valuable tool that helps in various situations, from premature returns to exceptions. With ScopeGuard, you can finally write exception-safe code with reasonable ease and understand and maintain it just as easily.

Every tool comes with a use recommendation, and ScopeGuard is no exception. You should use ScopeGuard as it was intended — as an automatic variable in functions. You should not hold ScopeGuard objects as member variables, try to put them in vectors, or allocate them on the heap. For these purposes, the downloadable code contains a Janitor class, which does exactly what ScopeGuard does, but in a more general way — at the expense of some efficiency.

Conclusion

We have presented the issues that arise in writing exception-safe code. After discussing a couple of ways of achieving exception safety, we have introduced a generic solution. ScopeGuard uses several generic programming techniques to let you prescribe function and member function calls when a ScopeGuard variable exits a scope. Optionally, you can dismiss the ScopeGuard object. ScopeGuard is useful when you need to perform automatic cleanup of resources. This idiom is important when you want to assemble an operation out of several atomic operations, each of which could fail.

Acknowledgements

The authors would like to thank to Mihai Antonescu for reviewing this paper and for making useful corrections and suggestions.

Bibliography

[1] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison Wesley, 1997), page 366.

[2] Herb Sutter. Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions (Addison-Wesley, 2000).


Andrei Alexandrescu is a development manager at RealNetworks Inc. (www.realnetworks.com), based in Seattle, WA. He may be contacted at www.moderncppdesign.com.

Petru Marginean is senior C++ developer for Plural, New York. He can be reached at petrum@hotmail.com.


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.
 

Video