В этом разделе представлены некоторые альтернативные и связанные работы с Boost.ScopeExit.
Это пример использования плохо разработанного класса file
. Пример file
не закрывает файл в своем деструкторе, программист, как ожидается, назовет функцию Close
. Например (см. также 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;
}
При этом следует отметить следующие вопросы:
- Объект
passwd
определяется за пределами блока try
, поскольку этот объект требуется внутри блока catch
для закрытия файла. - Объект
passwd
не полностью построен до тех пор, пока не вернется функция open
. - При открытии бросков не следует называть
passwd. Close()
, отсюда и призыв к passwd.is_open()
.
Подход Boost.ScopeExit не имеет ни одного из этих вопросов. Например (см. также 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 абсолютно идеально подходит для класса file
, представленного выше. Использование правильно разработанного file
класс будет выглядеть так:
try {
file passwd("/etc/passwd");
} catch(...) {
std::clog << "could not get user info" << std::endl;
throw;
}
Однако, используя RAII для создания сильной гарантии может ввести много неиспользуемых типов RAII. Например:
persons_.push_back(a_person);
pop_back_if_not_commit pop_back_if_not_commit_guard(commit, persons_);
Класс pop_back_if_not_commit
либо определяется из сферы охвата, либо как местный класс:
class pop_back_if_not_commit {
bool commit_;
std::vector<person>& vec_;
~pop_back_if_not_commit() {
if(!commit_) vec_.pop_back();
}
};
В некоторых случаях сильная гарантия может быть выполнена с помощью стандартных утилит:
std::auto_ptr<Person> superman_ptr(new superman());
persons_.push_back(superman_ptr.get());
superman_ptr.release();
Boost.Multi-Index.
.
Представьте, что новый курс валюты введен до совершения транзакции (см. также []):
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;
Если транзакция не завершена, валюта должна быть удалена из rates
. Это можно сделать с помощью ScopeGuard и Boost.Lambda (или 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;
При этом следует отметить следующие вопросы:
- Boost.Lambda выражений трудно писать правильно (например, перегруженные функции должны быть явно отброшены, как показано в примере выше).
- Условие в
if_
выражение относится к commit
переменной косвенно через _1
местоодержатель уменьшая читаемость. - Установка точки разрыва внутри
if_[...]
требует глубокого знания Boost.Lambda и методов отладки.
Этот код будет выглядеть намного лучше с C++11 lambdas:
ON_BLOCK_EXIT(
[currency_rate_inserted, &commit, &rates, ¤cy]() {
if(currency_rate_inserted && !commit) rates.erase(currency);
}
);
commit = true;
С Boost.ScopeExit мы можем просто сделать следующее (см. также 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 похож на функцию scope(exit), встроенную в язык программирования D.
Любопытный читатель может заметить, что библиотека не реализует scope(success)
и scope(failure)
языка D. К сожалению, это невозможно в C++, потому что условия неудачи или успеха не могут быть определены, позвонив по телефону std::uncaught_ exception
(см. Guru of the Week #47 для деталей о std::uncaught_ exception
и если он имеет какое-либо хорошее применение вообще). Однако это не большая проблема, потому что эти две конструкции D могут быть выражены в терминах scope(exit) и bool commit
переменная (аналогично некоторым примерам, представленным в разделе Tutorial).
Используя C++11 lambdas, относительно легко реализовать Boost.ScopeExit конструкцию. Например (см. также 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) {
if(!commit) persons_.pop_back();
});
commit = true;
}
Однако эта библиотека позволяет программировать Boost.ScopeExit конструкцию таким образом, чтобы она была переносной между компиляторами C++03 и C++11.