Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Annex: Alternatives

This section presents some alternatives and work related 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:

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:

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:

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 more 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 as explained 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_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, &persons_](void) { // Use C++11 lambda.
        if(!commit) persons_.pop_back();
    });

    // ...

    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