Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Annex: Alternatives

This section presents some alternative and related work to Boost.ScopeExit.

Try-Catch

This is an example of using a badly designed file class. An instance of file does not close the file in its destructor, a programmer is expected to call the close member function explicitly. For example (see also try_catch.cpp):

file passwd;
try {
    passwd.open("/etc/passwd");
    // ...
    passwd.close();
} catch(...) {
    std::clog << "could not get user info" << std::endl;
    if(passwd.is_open()) passwd.close();
    throw;
}

Note the following issues with this approach:

  1. The passwd object is defined outside of the try block because this object is required inside the catch block to close the file.
  2. The passwd object is not fully constructed until after the open member function returns.
  3. If opening throws, the passwd.close() should not be called, hence the call to passwd.is_open().

The Boost.ScopeExit approach does not have any of these issues. For example (see also try_catch.cpp):

try {
    file passwd("/etc/passwd");
    BOOST_SCOPE_EXIT(&passwd) {
        passwd.close();
    } BOOST_SCOPE_EXIT_END
} catch(...) {
    std::clog << "could not get user info" << std::endl;
    throw;
}

RAII

RAII is absolutely perfect for the file class introduced above. Use of a properly designed file class would look like:

try {
    file passwd("/etc/passwd");
    // ...
} catch(...) {
    std::clog << "could not get user info" << std::endl;
    throw;
}

However, using RAII to build up a strong guarantee could introduce a lot of non-reusable RAII types. For example:

persons_.push_back(a_person);
pop_back_if_not_commit pop_back_if_not_commit_guard(commit, persons_);

The pop_back_if_not_commit class is either defined out of the scope or as a local class:

class pop_back_if_not_commit {
    bool commit_;
    std::vector<person>& vec_;
    // ...
    ~pop_back_if_not_commit() {
        if(!commit_) vec_.pop_back();
    }
};

In some cases strong guarantee can be accomplished with standard utilities:

std::auto_ptr<Person> superman_ptr(new superman());
persons_.push_back(superman_ptr.get());
superman_ptr.release(); // persons_ successfully took ownership

Or with specialized containers such as Boost.PointerContainer or Boost.Multi-Index.

Scope Guards

Imagine that a new currency rate is introduced before performing a transaction (see also []):

bool commit = false;
std::string currency("EUR");
double rate = 1.3326;
std::map<std::string, double> rates;
bool currency_rate_inserted =
        rates.insert(std::make_pair(currency, rate)).second;
// Transaction...

If the transaction does not complete, the currency must be erased from rates. This can be done with ScopeGuard and Boost.Lambda (or Boost.Phoenix):

using namespace boost::lambda;

ON_BLOCK_EXIT(
    if_(currency_rate_inserted && !_1) [
        bind(
            static_cast<
                std::map<std::string, double>::size_type
                (std::map<std::string, double>::*)(std::string const&)
            >(&std::map<std::string, double>::erase)
          , &rates
          , currency
        )
    ]
  , boost::cref(commit)
  );

// ...

commit = true;

Note the following issues with this approach:

  1. Boost.Lambda expressions are hard to write correctly (e.g., overloaded functions must be explicitly casted, as demonstrated in the example above).
  2. The condition in the if_ expression refers to commit variable indirectly through the _1 placeholder reducing readability.
  3. Setting a breakpoint inside if_[...] requires in-depth knowledge of Boost.Lambda and debugging techniques.

This code will look much better with C++11 lambdas:

ON_BLOCK_EXIT(
    [currency_rate_inserted, &commit, &rates, &currency]() {
        if(currency_rate_inserted && !commit) rates.erase(currency);
    }
);

// ...

commit = true;

With Boost.ScopeExit we can simply do the following (see also scope_guard.cpp):

BOOST_SCOPE_EXIT(currency_rate_inserted, &commit, &rates, &currency) {
    if(currency_rate_inserted && !commit) rates.erase(currency);
} BOOST_SCOPE_EXIT_END

// ...

commit = true;

The D Programming Language

Boost.ScopeExit is similar to scope(exit) feature built into the D programming language.

A curious reader may notice that the library does not implement scope(success) and scope(failure) of the D language. Unfortunately, these are not possible in C++ because failure or success conditions cannot be determined by calling std::uncaught_exception (see Guru of the Week #47 for details about std::uncaught_exception and if it has any good use at all). However, this is not a big problem because these two D's constructs can be expressed in terms of scope(exit) and a bool commit variable (similarly to some examples presented in the Tutorial section).

C++11 Lambdas

Using C++11 lambdas, it is relatively easy to implement the Boost.ScopeExit construct. For example (see also world_cxx11_lambda.cpp):

#include <functional>

struct scope_exit {
    scope_exit(std::function<void (void)> f) : f_(f) {}
    ~scope_exit(void) { f_(); }
private:
    std::function<void (void)> f_;
};

void world::add_person(person const& a_person) {
    bool commit = false;

    persons_.push_back(a_person);
    scope_exit on_exit1([&commit, this](void) { // Use C++11 lambda.
        if(!commit) persons_.pop_back(); // `persons_` via captured `this`.
    });

    // ...

    commit = true;
}

However, this library allows to program the Boost.ScopeExit construct in a way that is portable between C++03 and C++11 compilers.


PrevUpHomeNext