Раз уж взялись, что мешает вставить в наш умный указатель с поддержкой отмены не один, а несколько предыдущих состояний объекта? Ничего. Только чтобы потом несколько раз не переделывать, решим несколько чисто технических моментов:
Какую структуру будем использовать в качестве коллекции состояний ? Можно взять стек, можно кольцевой буфер, а можно и карту (словарь, хэш-таблицу); стек явно проще, зато за кольцевым буфером можно не следить вообще, пусть устаревшие состояния пропадают бесследно (конечно по желанию).
Определимся с семантикой. Смешивать значения и указатели не стоит, верный путь заработать себе геморрой. У меня не оказалось под рукой подходящего стека, и я написал для этого Шага два варианта - один хранит значения, другой - указатели; первый стек сначала казался проще, но использующий его класс указателя оказался ощутимо сложнее по простой причине - функции стека с указателями могут возвращать NULL, а это совсем немало.
Оформим все в виде шаблонов; вообще контейнеры просто просятся быть шаблонами, а 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();
t = a->get();
a.PopOne();
t = a->get();
a.Rollback();
t = a->get();
return 0;
}