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

Tutorial

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

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 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.

[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 call). This is true for all Boost.ScopeExit macros (including BOOST_SCOPE_EXIT_TPL and BOOST_SCOPE_EXIT_ALL seen below) 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.
    person::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

It is not possible to capture the object 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.

A Boost.ScopeExit declaration can also capture 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)). [3] 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;
}

(Both compilers with and without variadic macros use this same syntax for capturing no variable, see the No Variadic Macros section for more information.)

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. [4]

Following the same syntax adopted by C++11 lambda functions, 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;
    // Capture all by reference `&`, but `checkpoint` and `this` (C++11 only).
    BOOST_SCOPE_EXIT_ALL(&, checkpoint, this) { // Use `this` (not `this_`).
        if(checkpoint == p.evolution) this->persons_.pop_back();
    }; // Use `;` (not `SCOPE_EXIT_END`).

    // ...

    checkpoint = ++p.evolution;

    // Assign new identifier to the person.
    person::id_t const prev_id = p.id;
    p.id = next_id_++;
    // Capture all by value `=`, but `p` (C++11 only).
    BOOST_SCOPE_EXIT_ALL(=, &p) {
        if(checkpoint == p.evolution) {
            this->next_id_ = p.id;
            p.id = prev_id;
        }
    };

    // ...

    checkpoint = ++p.evolution;
}

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

Note that the BOOST_SCOPE_EXIT_ALL macro follows the C++11 lambda function syntax which is unfortunately different from the BOOST_SCOPE_EXIT macro syntax. In particular:

  1. The BOOST_SCOPE_EXIT_ALL macro cannot capture data members without capturing the object this while that is not the case for BOOST_SCOPE_EXIT. [5]
  2. The BOOST_SCOPE_EXIT_ALL macro captures the object in scope using this instead of this_. [6]
  3. The BOOST_SCOPE_EXIT_ALL body is terminated by a semicolon ; instead than by the BOOST_SCOPE_EXIT_END macro.

If programmers define the configuration macro BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS then the BOOST_SCOPE_EXIT macro implementation will use C++11 lamda functions and the BOOST_SCOPE_EXIT macro will follow the same syntax of BOOST_SCOPE_EXIT_ALL macro, which is the C++11 lambda function syntax. However, BOOST_SCOPE_EXIT will no longer be backward compatible and older code using BOOST_SCOPE_EXIT might no longer compile (if data members were explicitly captured).

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. [7] The BOOST_SCOPE_EXIT_TPL macro has the exact same syntax of BOOST_SCOPE_EXIT. For example (see also world_tpl.cpp):

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;
}

It is recommended to always use BOOST_SCOPE_EXIT_TPL within templates so to maximize portability among different compilers.

In general, it is not possible to expand the BOOST_SCOPE_EXIT, BOOST_SCOPE_EXIT_TPL, BOOST_SCOPE_EXIT_END, and BOOST_SCOPE_EXIT_ALL macros multiple times on the same line. [8]

Therefore, this library provides additional macros BOOST_SCOPE_EXIT_ID, BOOST_SCOPE_EXIT_ID_TPL, BOOST_SCOPE_EXIT_END_ID, and BOOST_SCOPE_EXIT_ALL_ID which can be expanded multiple times on the same line as long as programmers specify a unique identifiers as the macros' first parameters. The unique identifier can be any token (not just numeric) that can be concatenated by the C++ preprocessor (e.g., scope_exit_number_1_at_line_123). [9]

The BOOST_SCOPE_EXIT_ID, BOOST_SCOPE_EXIT_ID_TPL, and BOOST_SCOPE_EXIT_ALL_ID macros accept a capture list using the exact same syntax as BOOST_SCOPE_EXIT and BOOST_SCOPE_EXIT_ALL respectively. For example (see also same_line.cpp):

#define SCOPE_EXIT_INC_DEC(variable, offset) \
    BOOST_SCOPE_EXIT_ID(BOOST_PP_CAT(inc, __LINE__), /* unique ID */ \
            &variable, offset) { \
        variable += offset; \
    } BOOST_SCOPE_EXIT_END_ID(BOOST_PP_CAT(inc, __LINE__)) \
    \
    BOOST_SCOPE_EXIT_ID(BOOST_PP_CAT(dec, __LINE__), \
            &variable, offset) { \
        variable -= offset; \
    } BOOST_SCOPE_EXIT_END_ID(BOOST_PP_CAT(dec, __LINE__))

#define SCOPE_EXIT_INC_DEC_TPL(variable, offset) \
    BOOST_SCOPE_EXIT_ID_TPL(BOOST_PP_CAT(inc, __LINE__), \
            &variable, offset) { \
        variable += offset; \
    } BOOST_SCOPE_EXIT_END_ID(BOOST_PP_CAT(inc, __LINE__)) \
    \
    BOOST_SCOPE_EXIT_ID_TPL(BOOST_PP_CAT(dec, __LINE__), \
            &variable, offset) { \
        variable -= offset; \
    } BOOST_SCOPE_EXIT_END_ID(BOOST_PP_CAT(dec, __LINE__))

#define SCOPE_EXIT_ALL_INC_DEC(variable, offset) \
    BOOST_SCOPE_EXIT_ALL_ID(BOOST_PP_CAT(inc, __LINE__), \
            =, &variable) { \
        variable += offset; \
    }; \
    BOOST_SCOPE_EXIT_ALL_ID(BOOST_PP_CAT(dec, __LINE__), \
            =, &variable) { \
        variable -= offset; \
    };

template<typename T>
void f(T& x, T& delta) {
    SCOPE_EXIT_INC_DEC_TPL(x, delta) // Multiple scope exits on same line.
    BOOST_TEST(x == 0);
}

int main(void) {
    int x = 0, delta = 10;

    {
        SCOPE_EXIT_INC_DEC(x, delta) // Multiple scope exits on same line.
    }
    BOOST_TEST(x == 0);

    f(x, delta);

#ifndef BOOST_NO_CXX11_LAMBDAS
    {
        SCOPE_EXIT_ALL_INC_DEC(x, delta) // Multiple scope exits on same line.
    }
    BOOST_TEST(x == 0);
#endif // LAMBDAS

    return boost::report_errors();
}

As shown by the example above, the BOOST_SCOPE_EXIT_ID, BOOST_SCOPE_EXIT_ID_TPL, BOOST_SCOPE_EXIT_END_ID, and BOOST_SCOPE_EXIT_ALL_ID macros are especially useful when it is necessary to invoke them multiple times within user-defined macros (because the C++ preprocessor expands all nested macros on the same line).



[3] Rationale. Unfortunately, it is not possible to simply invoke the Boost.ScopeExit macro with no parameters as in BOOST_SCOPE_EXIT() because the C++ 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).

[4] Rationale. The BOOST_SCOPE_EXIT_ALL macro is only defined on C++11 compilers for which the Boost.Config macro BOOST_NO_CXX11_LAMBDAS is not defined. Using BOOST_SCOPE_EXIT_ALL on C++03 compilers for which BOOST_NO_CXX11_LAMBDAS is defined will generate (possibly cryptic) compiler errors. 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(=) cannot be distinguished from BOOST_SCOPE_EXIT(void) or BOOST_SCOPE_EXIT(this_) using the C++ preprocessor given that the symbols & and = are neither prefxied nor postfixed by alphanumeric tokens (this is not an issue for BOOST_SCOPE_EXIT_ALL which always has the non-alphanumeric & or = as the first capture so the first capture tokens are simply never compared with neither void nor this_ for this macro).

[5] At present, there seems to be some discussion to allow C++11 lambda functions to capture data members without capturing the object this. If the C++11 standard were changed to allow this, the BOOST_SCOPE_EXIT_ALL macro syntax could be extended to be a superset of the BOOST_SCOPE_EXIT macro while keeping full backward compatibility.

[6] On compilers that support the use of the typename outside templates as allowed by the C++11 standard, BOOST_SCOPE_EXIT_ALL can use both this and this_ to capture the object in scope (notably, this is not the case for the MSVC 10.0 compiler).

[7] Rationale. 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.

[8] Rationale. The library macros internally use __LINE__ to generate unique identifiers. Therefore, if the same macro is expanded more than on time on the same line, the generated identifiers will no longer be unique and the code will not compile. (This restriction does not apply to MSVC and other compilers that provide the non-standard __COUNTER__ macro.)

[9] Because there are restrictions on the set of tokens that the C++ preprocessor can concatenate and because not all compilers correctly implement these restrictions, it is in general recommended to specify unique identifiers as a combination of alphanumeric tokens.


PrevUpHomeNext