...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
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 |
---|---|
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 |
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.