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

This is the documentation for an old version of Boost. Click here to view this page for the latest version.
PrevUpHomeNext

Tutorial

Capturing Variables
Capturing The Object this
Capturing No Variable
Capturing All Variables (C++11 Only)
GCC Template Workaround

This section illustrates how to use this library.

Imagine that we want to make many modifications to data members of some world class in its world::add_person member function. We start with adding a new person object to a vector of persons:

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

    persons_.push_back(a_person);           // (1) direct action
    ...

Some operations down the road may throw an exception and all changes to involved objects should be rolled back. This all-or-nothing semantic is also known as strong guarantee.

In particular, the last added person must be deleted from persons_ if the function throws. All we need is to define a delayed action (release of a resource) right after the direct action (resource acquisition). For example (see also world.cpp):

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

    persons_.push_back(a_person);           // (1) direct action
    // Following block is executed when the enclosing scope exits.
    BOOST_SCOPE_EXIT(&commit, &persons_) {
        if(!commit) persons_.pop_back();    // (2) rollback action
    } BOOST_SCOPE_EXIT_END

    // ...                                  // (3) other operations

    commit = true;                          // (4) disable rollback actions
}

The block below point (1) is a Boost.ScopeExit declaration. Unlike point (1), an execution of the Boost.ScopeExit body will be delayed until the end of the current scope. In this case it will be executed either after point (4) or on any exception. (On various versions of the GCC compiler, it is necessary to use BOOST_SCOPE_EXIT_TPL instead of BOOST_SCOPE_EXIT within templates, see later in this section for details.)

The Boost.ScopeExit declaration starts with the BOOST_SCOPE_EXIT macro invocation which accepts a comma-separated list of captured variables (a Boost.Preprocessor sequence is also accepted here for compilers that do not support variadic macros and for backward compatibility with older versions of this library, see the No Variadic Macros section). If a capture starts with the ampersand sign &, a reference to the captured variable will be available inside the Boost.ScopeExit body; otherwise, a copy of the variable will be made after the Boost.ScopeExit declaration at point (1) and only the copy will be available inside the body (in this case, the captured variable's type must be CopyConstructible).

In the example above, the variables commit and persons_ are captured by reference because the final value of the commit variable should be used to determine whether to execute rollback actions or not and the action should modify the persons_ object, not its copy. This is the most common case but passing a variable by value is sometimes useful as well.

Finally, the end of the Boost.ScopeExit body must be marked by the BOOST_SCOPE_EXIT_END macro which must follow the closing curly bracket } of the Boost.ScopeExit body. On C++11 it is also possible (but not required) to use a semi-column ; instead of the BOOST_SCOPE_EXIT_END macro. [2]

[Important] Important

In order to comply with the STL exception safety requirements, the Boost.ScopeExit body must never throw (because the library implementation executes the body within a destructor). This is true for all Boost.ScopeExit macros (including BOOST_SCOPE_EXIT_TPL and BOOST_SCOPE_EXIT_ALL) on both C++03 and C++11.

Consider a more complex example where world::add_person can save intermediate states at some point and roll back to the last saved state. We use person::evolution_ to store a version of the changes and increment it to cancel all rollback actions associated with those changes. If we pass a current value of evolution_ stored in the checkpoint variable by value, it remains unchanged within the Boost.ScopeExit body so we can compare it with the final value of evolution_. If the latter was not incremented since we saved it, the rollback action inside the Boost.ScopeExit body should be executed. For example (see also world_checkpoint.cpp):

void world::add_person(person const& a_person) {
    persons_.push_back(a_person);

    // This block must be no-throw.
    person& p = persons_.back();
    person::evolution_t checkpoint = p.evolution_;
    BOOST_SCOPE_EXIT(checkpoint, &p, &persons_) {
        if(checkpoint == p.evolution_) persons_.pop_back();
    } BOOST_SCOPE_EXIT_END

    // ...

    checkpoint = ++p.evolution_;

    // Assign new identifier to the person.
    world::id_t const prev_id = p.id_;
    p.id_ = next_id_++;
    BOOST_SCOPE_EXIT(checkpoint, &p, &next_id_, prev_id) {
        if(checkpoint == p.evolution_) {
            next_id_ = p.id_;
            p.id_ = prev_id;
        }
    } BOOST_SCOPE_EXIT_END

    // ...

    checkpoint = ++p.evolution_;
}

When multiple Boost.ScopeExit blocks are declared within the same enclosing scope, the Boost.ScopeExit bodies are executed in the reversed order of their declarations.

Within a member function, it is also possible to capture the object this. However, the special symbol this_ must be used instead of this in the Boost.ScopeExit declaration and body to capture and access the object. For example (see also world_this.cpp):

BOOST_SCOPE_EXIT(&commit, this_) { // Capture object `this_`.
    if(!commit) this_->persons_.pop_back();
} BOOST_SCOPE_EXIT_END

On C++11, it is possible (but not required) to directly use this instead of the special symbol this_. [3] For example (see also world_this.cpp):

BOOST_SCOPE_EXIT(&commit, this) { // Use `this` (C++11).
    if(!commit) this->persons_.pop_back();
}; // Use `;` instead of `BOOST_SCOPE_EXIT_END` (C++11).

It is never possible to capture the object this_ (or this) by reference because C++ does not allow to take a reference to this. If the enclosing member function is constant then the captured object will also be constant, otherwise the captured object will be mutable.

It is possible to declare Boost.ScopeExit code that captures no variable. In this case, the list of captured variables is replaced by the void keyword (similarly to the C syntax that allows to declare a function with no parameter using result-type function-name(void)). [4] For example, this can be useful when the Boost.ScopeExit body only needs to access global variables (see also world_void.cpp):

struct world_t {
    std::vector<person> persons;
    bool commit;
} world; // Global variable.

void add_person(person const& a_person) {
    world.commit = false;
    world.persons.push_back(a_person);

    BOOST_SCOPE_EXIT(void) { // No captures.
        if(!world.commit) world.persons.pop_back();
    } BOOST_SCOPE_EXIT_END

    // ...

    world.commit = true;
}

This same syntax is supported for both compilers with and without variadic macro support.

On C++11 compliers, it is also possible to capture all the variables in scope without naming them one by one using the special macro BOOST_SCOPE_EXIT_ALL instead of BOOST_SCOPE_EXIT. [5]

Following the same syntax adopted by C++11 lambdas, the BOOST_SCOPE_EXIT_ALL macro accepts a comma-separated list of captures which must start with either & or = to capture all variables in scope respectively by reference or by value (note that no variable name is specified by these leading captures). Additional captures of specific variables can follow the leading & or = and they will override the default reference or value captures. For example (see also world_checkpoint_all.cpp):

void world::add_person(person const& a_person) {
    persons_.push_back(a_person);

    // This block must be no-throw.
    person& p = persons_.back();
    person::evolution_t checkpoint = p.evolution_;
    BOOST_SCOPE_EXIT_ALL(&, checkpoint, this_) { // Capture all by ref (C++11).
        if(checkpoint == p.evolution_) this_->persons_.pop_back();
    } BOOST_SCOPE_EXIT_END

    // ...

    checkpoint = ++p.evolution_;

    // Assign new identifier to the person.
    world::id_t const prev_id = p.id_;
    p.id_ = next_id_++;
    BOOST_SCOPE_EXIT_ALL(=, &p, this) { // Capture all by value, `this` (C++11).
        if(checkpoint == p.evolution_) {
            this->next_id_ = p.id_;
            p.id_ = prev_id;
        }
    }; // Use `;` instead of `SCOPE_EXIT_END` (C++11).

    // ...

    checkpoint = ++p.evolution_;
}

The first Boost.ScopeExit declaration captures all variables in scope by reference but checkpoint and this_ which are explicitly captured by value (in particular, p and persons_ are captured by reference). The second Boost.ScopeExit declaration instead captures all variables in scope by value but p which is captured by reference (in particular, checkpoint, prev_id, and this are captured by value).

Various versions of the GCC compiler do not compile BOOST_SCOPE_EXIT inside templates (see the Reference section for more information). As a workaround, BOOST_SCOPE_EXIT_TPL should be used instead of BOOST_SCOPE_EXIT in these cases: [6]

template<typename Person>
void world<Person>::add_person(Person const& a_person) {
    bool commit = false;
    persons_.push_back(a_person);

    BOOST_SCOPE_EXIT_TPL(&commit, this_) { // Use `_TPL` postfix.
        if(!commit) this_->persons_.pop_back();
    } BOOST_SCOPE_EXIT_END

    // ...

    commit = true;
}

The BOOST_SCOPE_EXIT_TPL macro has the exact same syntax of BOOST_SCOPE_EXIT.



[2] The macro BOOST_SCOPE_EXIT_END can still be used on C++11 to write portable code that can be used on both C++03 and C++11 compilers. Using ; instead of BOOST_SCOPE_EXIT_END on C++03 compilers will generate a (possibly cryptic) compiler error.

[3] The special symbol this_ can still be used on C++11 to write portable code that can be used on both C++03 and C++11 compilers. Unfortunately, using this instead of this_ on C++03 compilers leads to undefined behaviour (it will likely generate a compiler error but that is not guaranteed).

[4] Rationale. Unfortunately, it is not possible to simply invoke the Boost.ScopeExit macro with no parameters BOOST_SCOPE_EXIT() because the preprocessor cannot detect emptiness of a macro parameter when the parameter can start with a non-alphanumeric symbol (which is the case when capturing a variable by reference &variable).

[5] Rationale. The BOOST_SCOPE_EXIT_ALL macro is only defined on C++11 compilers. Using BOOST_SCOPE_EXIT_ALL on C++03 compilers will generate a (possibly cryptic) compiler error. Note that a new macro BOOST_SCOPE_EXIT_ALL needed to be introduced instead of reusing BOOST_SCOPE_EXIT because BOOST_SCOPE_EXIT(&) and BOOST_SCOPE_EXIT(=) could not be distinguished from BOOST_SCOPE_EXIT(void) or BOOST_SCOPE_EXIT(this_) using the preprocessor because the symbols & and = are neither prefxied or postfixed by alphanumeric tokens (this is not an issue for BOOST_SCOPE_EXIT_ALL which always has & or = as the first capture so the first capture token is never compared with neither void or this_ for this macro).

[6] GCC versions compliant with C++11 do not present this issue and given that BOOST_SCOPE_EXIT_ALL is only available on C++11 compilers, there is no need for a BOOST_SCOPE_EXIT_ALL_TPL macro.


PrevUpHomeNext