...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 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 | |
---|---|
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 |
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:
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]
BOOST_SCOPE_EXIT_ALL
macro captures the object in scope using this
instead of this_
. [6]
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.