Alternatives

| Home | Libraries | People | FAQ | More

## Alternatives

#### try-catch

This is an example of using a badly designed `File` class. An instance of `File` doesn't close a file in a destructor, a programmer is expected to call the `close` member function explicitly.

    File passwd;
    try {
        passwd.open("/etc/passwd");
        // ...
        passwd.close();
    }
    catch(...) {
        log("could not get user info");
        if(passwd.is_open())
            passwd.close();
        throw;
    }

Note the following:

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

ScopeExit doesn't have any of these problems:

    try {
        File passwd("/etc/passwd");
        BOOST_SCOPE_EXIT( (&passwd) ) {
            passwd.close();
        } BOOST_SCOPE_EXIT_END
        // ...
    }
    catch(...) {
        log("could not get user info");
        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(...) {
        log("could not get user info");
        throw;
    }

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

    m_persons.push_back(person);
    pop_back_if_not_commit pop_back_if_not_commit_guard(commit, m_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 m_commit;
        std::vector<Person>& m_vec;
        // ...
        ~pop_back_if_not_commit() {
            if(!m_commit)
                m_vec.pop_back();
        }
    };

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

    std::auto_ptr<Person> spSuperMan(new Superman);
    m_persons.push_back(spSuperMan.get());
    spSuperMan.release(); // m_persons successfully took ownership.

or with specialized containers such as Boost Pointer Container Library or Boost Multi-Index Containers Library.

#### ScopeGuard

Imagine that you add a new currency rate:

    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;

and then continue a transaction. If it cannot be completed, you erase the currency from `rates`. This is how you can do this with ScopeGuard and Boost.Lambda:

    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 that

- Boost.lambda expressions are hard to write correctly, for example, overloaded function must be explicitly casted, as demonstrated in this example,
- condition in `if_` expression refers to `commit` variable indirectly through the `_1` placeholder,
- setting a breakpoint inside `if_[ ... ]` requires in-depth knowledge of Boost.Lambda and debugging techniques.

This code will look much better with native lambda expressions proposed for C++0x:

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

With ScopeExit you can simply do

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

    // ...

    commit = true;

#### C++0x

In future releases ScopeExit will take advantages of C++0x features.

- Passing capture list as Boost.Preprocessor sequence will be replaced with a traditional macro invocation style:

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

- `BOOST_SCOPE_EXIT_END` will be replaced with a semicolon:

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

- Users will be able to capture local variables implicitly with lambda capture defaults `&` and `=`:

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

- It will be possible to capture `this` pointer.

#### The D Programming Language

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

A curious reader may notice that the library doesn't implement `scope(success)` and `scope(failure)` of the D language. Unfortunately, it's not possible in C++ because failure or success condition cannot be determined by calling `std::uncaught_exception`. It's not a big problem, though. These two constructs can be expressed in terms of scope(exit) and a `bool commit` variable as explained in Tutorial. Refer to Guru of the Week #47 for more details about `std::uncaught_exception` and if it has any good use at all.