Hatred's Log Place

DON'T PANIC!

Jan 12, 2015 - 2 minute read - programming c++

C++11: паттерн Transaction

Переосмысление реализации паттерна 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 может сильно сказаться поглощение стека.