Переосмысление реализации паттерна
Transaction, опубликованного в
“Inside C++”, в рамках стандарта C++11 с блек… variadict templates и хранением данных на стеке.
Итак, что не нравится в оригинальном способе? Мне не нравится скрытое использование Type erasure, которое требует динамической аллокации маленьких кусочков памяти для каждого хранимого параметра. Так что делаем полностью статическую версию, хранение параметров происходит на стеке, параметров любое количество.
Реализация:
#include <iostream>
#include <tuple>
template<typename... Args>
struct Transaction
{
Transaction(Args& ... args)
: refs(args...),
vals(args...)
{
}
~Transaction()
{
if (incomplete) {
revert();
}
}
void commit()
{
incomplete = false;
}
// Disable copy and allow move
Transaction(const Transaction&) = delete;
Transaction& operator=(const Transaction&) = delete;
Transaction(Transaction&&) = default;
Transaction& operator=(Transaction&&) = default;
private:
void revert()
{
refs = vals;
}
std::tuple<Args& ...> refs;
std::tuple<Args ...> vals;
bool incomplete = true;
};
template<typename... Args>
Transaction<Args...> make_transaction(Args& ... args)
{
return Transaction<Args...>(args...);
}
int main()
{
std::string name = "Angelina";
float bosom = 85.3f;
float waist = 68.5f;
float pelvis = 88.8f;
try {
auto trn = make_transaction(name, bosom, waist, pelvis);
name = "Olga";
bosom = 102.4f;
waist = 59.3f;
pelvis = 92.3f;
// ...
// Здесь что-то происходит
// ...
throw std::runtime_error("Wife is coming!");
trn.commit();
} catch(std::exception const&) {
std::cout << "Hi, dear!<br/>n" << std::endl;
}
return 0;
}
Вспомогательная функция make_transaction()
вкупе с auto
позволяет уйти от ручного задания типа хранимых параметров и точного типа транзакции. Для хранения параметров и их начальных значений используется кортеж (появился в C++11).
Как видим, кода получилось даже меньше, чем в оригинальном варианте при схоже читабельности. Есть возможность реализовать подобное и в рамках C++98/2003, но решение выйдет сильно громоздким (посмотрите как реализацию boost::function для C++98/03).
Плюсы и минусы паттерна, описанные в оригинальной статье, никуда не уходят.
- Плюс реализации: отсутствие оверхеда на аллокацию памяти, раздолье для оптимизатора компилятора.
- Минус: нельзя в динамике добавить параметр в транзакцию, а в embedded может сильно сказаться поглощение стека.