KnigaRead.com/

Albert Makhmutov - Идиомы и стили С++

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Albert Makhmutov, "Идиомы и стили С++" бесплатно, без регистрации.
Перейти на страницу:

Бывает особенно приятно, когда занимаешься теорией. Занимаешься, думаешь: "ну никакой связи с жизнью, хоть бы минимум пользы"… и вдруг раз! и польза является во всей своей красе, блистая в лучах солнца и хрустя пачками денег. Что чувствовал Менделеев, когда после долгих изысканий, жутких таблиц, являвшихся ему в ночных кошмарах, вдруг получил-таки нормальную, не паленую, 40-градусную водку? Или Эйлер, когда, после терзаний и депрессий, извлек таки сопротивляющийся, визжащий и цепляющийся щупальцами и жвалами квадратный корень из минус единицы? К чему это я? А вот к чему: концепция smart-указателей может предложить простые и прозрачные решения для некоторых сложных задач; и если Вы считаете, что поддержка транзакций (а так же многоуровневой отмены и повтора) есть сложная задача, то смарты помогут Вам с замечательной легкостью.

Вспомним для начала, что делает транзакция:

1. Если транзакция началась, то все изменения в ней либо вносятся вместе, либо не вносятся вообще (это вообще определение транзакции).

2. Если клиент начал и не завершил транзакцию, то другие клиенты не видят его изменений.

3. Две транзакции не могут одновременно изменять одни и те же данные.

Начнем с первого пункта. Если мы хотим закрепить или отменить проведенные изменения, нужно хранить состояние объекта на заданный момент - начало транзакции, и в момент принятия решения или уничтожать предыдущее состояние (закрепление) или возвращаться к нему (отмена). Пусть обслуживанием занимается smart-указатель. Кладем в него два указателя - один на текущий объект, а второй - на объект, представляющий его предыдущее состояние, и три функции - старт, закрепление, отмена.

#include ‹iostream.h›

// Некий скромный класс.

class CSomeClass {

public:

 int x;

 int y;

};


// Его оплетка: smart-pointer с поддержкой отмены.

class CSimpleTr {

public:

 CSomeClass* that; // текущее значение

 CSomeClass* previos; // предыдущее значение

public:

 // конструктор-деструктор-присваивание-копирование

 CSimpleTr(): previos(NULL), that(new CSomeClass) {}

 CSimpleTr(const CSimpleTr& _st): that(new CSomeClass(*(_st.that))), previos(NULL) {}

 ~CSimpleTr() {delete that; delete previos;}

 CSimpleTr& operator=(const CSimpleTr& _st) {

  if (this!=&_st) {

   delete that;

   that = new CSomeClass(*(_st.that));

  }

  return *this;

 }

 // начало транзакции

 void BeginTrans() {

  delete previos;

  previos = that;

  that = new CSomeClass(*previos);

 }

 // закрепление

 void Commit() {

  delete previos;

  previos = NULL;

 }

 // отмена транзакции

 void Rollback() {

  if (previos != NULL) {

   delete that;

   that = previos;

   previos = NULL;

  }

 }

 // реализация указателя

 CSomeClass* operator-›() { return that; }

};


int main (void) {

 // проверим быстренько

 CSimpleTr lvPair;

 lvPair-›x = 5;

 lvPair-›y = 8;

 lvPair.BeginTrans();

 lvPair-›x = 7;

 lvPair-›y = 11;

 lvPair.Rollback();

 lvPair.BeginTrans();

 lvPair-›x = 7;

 lvPair-›y = 11;

 lvPair.Commit();

 return 0;

}

Что может быть проще? Семантика значений, очевидно. Классы должны иметь полный набор обязательных функций, как обычно; в нашем случае класс CSomeClass больно уж тривиален, поэтому сойдет и так. Класс CSimpleTr имеет смысл переписать в виде шаблона, если не хотите его заново переписывать для каждого нового клиента, да еще добавить функцию isStarted(). Функциональность на Ваш вкус и фантазию. MTS, например, восстановит отмененную транзакцию, если Вы после отмены сделаете закрепление: SetAbort(); SetComplete(); сработает так, как будто SetAbort(); не было вовсе.

Шаг 23 - Классы объектов, поддерживающие транзакции. Продолжение.

Раз уж взялись, что мешает вставить в наш умный указатель с поддержкой отмены не один, а несколько предыдущих состояний объекта? Ничего. Только чтобы потом несколько раз не переделывать, решим несколько чисто технических моментов:

1. Какую структуру будем использовать в качестве коллекции состояний? Можно взять стек, можно кольцевой буфер, а можно и карту (словарь, хэш-таблицу); стек явно проще, зато за кольцевым буфером можно не следить вообще, пусть устаревшие состояния пропадают бесследно (конечно по желанию).

2. Определимся с семантикой. Смешивать значения и указатели не стоит, верный путь заработать себе геморрой. У меня не оказалось под рукой подходящего стека, и я написал для этого Шага два варианта - один хранит значения, другой - указатели; первый стек сначала казался проще, но использующий его класс указателя оказался ощутимо сложнее по простой причине - функции стека с указателями могут возвращать NULL, а это совсем немало.

3. Оформим все в виде шаблонов; вообще контейнеры просто просятся быть шаблонами, а smart-указатели несомненно являются контейнерами.

Код ниже, а сейчас пояснения:

Класс CType просто проверочный, чтобы вкладывать в шаблоны; так проще отлаживать шаблон: сначала сделать контейнер-не-шаблон для класса Type, а потом просто приписать сверху объявления строку template‹Type›. Шаблон класса ampstack‹Type› - шаблон стека указателей; push сохраняет указатель, pop достает верхний указатель, isEmpty проверяет на пустоту, emptyAll очищает.

Шаблон класса MLTrans - наконец тот, который нам нужен. Указатель that хранит текущее значение, Push сохраняет текущее значение, PopOne делает однократную отмену, Rollback отменяет все изменения, до первоначального, Commit удаляет историю.

// Это маленький класс для проверки

class CType {

 int a;

public:

 void set (int _a) { a=_a; }

 int get (void) { return a; }

};


// Шаблон стека

template ‹class Type›

class ampstack {

private:

 int iTop; // верх стека

 int iSize; // размер стека

 Type** array; // массив указателей

public:

 // Конструктор-деструктор

 ampstack(int size=10) : iTop(0), iSize(size), array(new Type*[size]) {}

 ~ampstack() {

  for (int iCounter = 0; iCounter ‹ iTop; iCounter ++)

   if (*(array+iCounter)!= NULL) delete *(array+iCounter);

  delete[] array;

 }

 // Управление стеком

 // Направить указатель в стек

 void push (Type* _t) { array[iTop++]=_t; }

 // Вынуть указатель из стека

 Type* pop (void) {

  if (iTop == 0) return NULL;

  else return array[--iTop];

 }

 // Стек пуст?

 int isEmpty (void) { return iTop==0; }

 // Очистить стек

 void emptyAll (void) {

  for (int iCounter = 0; iCounter ‹ iTop; iCounter ++)

   if (*(array+iCounter)!= NULL) delete *(array+iCounter);

  iTop = 0;

 }

};


// Шаблон класса с многоуровневой отменой

template ‹class Type›

class MLTrans {

 typedef ampstack‹Type› stack;

private:

 Type* that; // Текущее значение

 stack history; // контейнер предыдущих значений

public:

 // конструктор-деструктор

 MLTrans(): that(new Type) {}

 ~MLTrans () { delete that; }

 // Сохранение текущего значения, aналог SAVE TRANSACTION в SQL серверах

 void Push() {

  history.push(that);

  that = new Type(*that);

 }

 // удаление промежуточных состояний

 void Commit () { history.emptyAll(); }

 // Откат на одну позицию; уничтожает текущее значение.

 void PopOne() {

  if (!history.isEmpty()) {

   delete that;

   that = history.pop();

  }

 }

 // Откат к началу транзакции.

 void Rollback() {

  Type* old = history.pop();

  Type* older = NULL;

  if (old!= NULL) {

   while ((older = history.pop())!= NULL) {

    delete old;

    old = older;

   }

   delete that;

   that = old;

  }

 }

 // Переопределенный operator-›

 Type* operator-›() { return that; }

}


// проверим работу

int main() {

 int t;

 MLTrans‹CType› a;

 a-›set(5);

 t = a-›get();

 a.Push();

 a-›set(6);

 t = a-›get();

 a.Push();

 t = a-›get();

 a-›set(7);

 t = a-›get();

 a.Push();

 t = a-›get();

 a-›set(9);

 t = a-›get();

// a.Push();

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*