...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 presents some alternative and related work to Boost.ScopeExit.
This is an example of using a badly designed file
class. An instance of file
does not close the file in its destructor, a programmer is expected to call
the close
member function explicitly.
For example (see also try_catch.cpp
):
file passwd; try { passwd.open("/etc/passwd"); // ... passwd.close(); } catch(...) { std::clog << "could not get user info" << std::endl; if(passwd.is_open()) passwd.close(); throw; }
Note the following issues with this approach:
passwd
object is defined
outside of the try
block because
this object is required inside the catch
block to close the file.
passwd
object is not
fully constructed until after the open
member function returns.
passwd.close()
should not be called, hence the call
to passwd.is_open()
.
The Boost.ScopeExit approach does not have
any of these issues. For example (see also try_catch.cpp
):
try { file passwd("/etc/passwd"); BOOST_SCOPE_EXIT(&passwd) { passwd.close(); } BOOST_SCOPE_EXIT_END } catch(...) { std::clog << "could not get user info" << std::endl; throw; }
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(...) { std::clog << "could not get user info" << std::endl; throw; }
However, using RAII to build up a strong guarantee could introduce a lot of non-reusable RAII types. For example:
persons_.push_back(a_person); pop_back_if_not_commit pop_back_if_not_commit_guard(commit, 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 commit_; std::vector<person>& vec_; // ... ~pop_back_if_not_commit() { if(!commit_) vec_.pop_back(); } };
In some cases strong guarantee can be accomplished with standard utilities:
std::auto_ptr<Person> superman_ptr(new superman()); persons_.push_back(superman_ptr.get()); superman_ptr.release(); // persons_ successfully took ownership
Or with specialized containers such as Boost.PointerContainer or Boost.Multi-Index.
Imagine that a new currency rate is introduced before performing a transaction (see also []):
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; // Transaction...
If the transaction does not complete, the currency must be erased from rates
. This can be done with ScopeGuard
and Boost.Lambda (or
Boost.Phoenix):
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 the following issues with this approach:
if_
expression refers to commit
variable indirectly through the _1
placeholder reducing readability.
if_[...]
requires in-depth knowledge of Boost.Lambda and debugging
techniques.
This code will look much better with C++11 lambdas:
ON_BLOCK_EXIT( [currency_rate_inserted, &commit, &rates, ¤cy]() { if(currency_rate_inserted && !commit) rates.erase(currency); } ); // ... commit = true;
With Boost.ScopeExit we can simply do the
following (see also scope_guard.cpp
):
BOOST_SCOPE_EXIT(currency_rate_inserted, &commit, &rates, ¤cy) { if(currency_rate_inserted && !commit) rates.erase(currency); } BOOST_SCOPE_EXIT_END // ... commit = true;
Boost.ScopeExit is similar to scope(exit) feature built into the D programming language.
A curious reader may notice that the library does not implement scope(success)
and scope(failure)
of the D
language. Unfortunately, these are not possible in C++ because failure or success
conditions cannot be determined by calling std::uncaught_exception
(see Guru of the Week #47
for details about std::uncaught_exception
and if it has any good
use at all). However, this is not a big problem because these two D's
constructs can be expressed in terms of scope(exit)
and a bool commit
variable (similarly to some examples presented in the Tutorial
section).
Using C++11 lambdas, it is relatively easy to implement the Boost.ScopeExit
construct. For example (see also world_cxx11_lambda.cpp
):
#include <functional> struct scope_exit { scope_exit(std::function<void (void)> f) : f_(f) {} ~scope_exit(void) { f_(); } private: std::function<void (void)> f_; }; void world::add_person(person const& a_person) { bool commit = false; persons_.push_back(a_person); scope_exit on_exit1([&commit, this](void) { // Use C++11 lambda. if(!commit) persons_.pop_back(); // `persons_` via captured `this`. }); // ... commit = true; }
However, this library allows to program the Boost.ScopeExit construct in a way that is portable between C++03 and C++11 compilers.