Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на C / C++ / C++ for real programmers.pdf
Скачиваний:
262
Добавлен:
02.05.2014
Размер:
2.04 Mб
Скачать

149

Класс LockPtr

Ага! Мы подошли к центральной идее всей концепции — указателям, которые разрешают обновление указываемого объекта. Предтранзакционный (предназначенный для отмены) образ хранится в ConstPtr, а текущий обновленный образ доступен только через LockPtr. Класс LockPtr содержит уже знакомые функции Rollback() и Commit(). В функции Snapshot() нет необходимости, поскольку LockPtr при необходимости создает образы в операторе ->.

template <class Type>

class LockPtr : public Lock { friend class ConstPtr<Type>; private:

ConstPtr<Type>* master_ptr; Type* new_image; Transaction* transaction;

LockPtr(Transaction* t, ConstPtr<Type>* cp); virtual ~LockPtr();

virtual void Rollback(); virtual void Commit();

public:

Type* operator->() const { return new_image; }

};

template <class Type>

LockPtr<Type>::LockPtr(Transaction* t, ConstPtr<Type>* cp)

: transaction(t), master_ptr(cp), new_image(new Type(*(cp->old_image)))

{

}

template <class Type> LockPtr<Type>::~LockPtr()

{

// В сущности происходит откат

delete new_image;

//

Отказаться от изменений

master_ptr->lock = NULL;

//

Оставить ConstPtr

}

template <class Type>

void LockPtr<Type>::Rollback()

{

delete new_image;

new_image = new Type(*(master_ptr->old_image));

}

 

template <class Type>

 

void LockPtr<Type>::Commit()

 

{

 

delete master_ptr->old_image;

 

master_ptr->old_image = new_image;

// Переместить в master_ptr

new_image = new Type(*new_image);

// Нужна новая копия

}

 

Деструктор объявлен закрытым, чтобы никто не мог напрямую удалить LockPtr. Вместо этого транзакция-владелец должна сделать это через базовый класс Lock. Функции Rollback() и Commit() объявлены виртуальными, чтобы с их помощью можно было решать задачи, относящиеся к

150

конкретному типу (например, создание и уничтожение образов). Обе функции после завершения оставляют ConstPtr заблокированным.

Создание и уничтожение объектов

Пора заполнить кое-какие пробелы. Раз уж наши транзакции достаточно сложны, чтобы для них была оправдана вся эта возня, они наверняка будут создавать или уничтожать объекты. Операции создания и уничтожения также должны быть отменяемыми. Если объект создавался, операция отмены должна его уничтожать, а если уничтожался — возвращать его из мертвых. Для этого придется внести изменения как в класс ConstPtr, так и в класс LockPtr. Мы уже сделали первый шаг в этом направлении, объявив деструктор ConstPtr закрытым, чтобы им могли воспользоваться только ConstPtr или его друзья. Давайте разберемся с оставшимися проблемами.

Изменения в классе ConstPtr

Несомненно, создание указываемого объекта представляет собой изменение и потому должно осуществляться через LockPtr. Но для того чтобы получить LockPtr, мы должны сначала иметь ConstPtr и его функцию Lock(). Следовательно, только что описанный конструктор ConstPtr работать не будет — он создает уникальный объект перед вызовом Lock(). ConstPtr должен находиться в состоянии NULL до тех пор, пока LockPtr не выделит память под объект и не закрепит эти изменения. В ConstPtr необходимо внести следующие изменения:

В конструкторе без аргументов присваивать переменной old_image значение NULL.

Добавить оператор !, который проверяет, равен ли адрес значению NULL.

Инициировать исключение в операторе ->, если адрес равен значению NULL.

Либо запретить копирование, либо присвоить копии old_image значение NULL.

Проблема с обычным конструктором копий ConstPtr заключается в том, что он может создать новую копию указываемого объекта, но не позволит отменить ее создание. Ниже приводится новая версия конструктора ConstPtr. Определения функций, не изменившиеся по сравнению с показанной выше упрощенной версией не показаны.

private:

ConstPtr(const ConstPtr&) : old_image(NULL), lock(NULL) {} public:

ConstPtr() : old_image(NULL), lock(NULL) {} bool operator!() { return old_image == NULL; } const Type* operator->() const

{

if (old_image == NULL) // Исключение

return old_image;

}

Изменения в классе LockPtr

Отныне LockPtr предстоит выполнять намного больше работы:

Он должен при необходимости создавать указываемый объект по требованию. Для этого в него будет добавлена функция Make().

Оператор -> должен инициировать исключение, если адрес равен NULL.

Ниже приведены определения только изменившихся функций.

// В объявлении LockPtr

public:

 

void Make();

// Создать новый указываемый объект

void Destroy();

// Уничтожить указываемый объект

Функция Make()
}

151

// Изменившиеся определения template <class Type>

LockPtr<Type>::LockPtr(Transaction* t, ConstPtr<Type>* cp)

:transaction(t), master_ptr(cp),

new_image(cp->old_image != NULL ? new Type(*(cp->old_image)) : NULL)

{

}

template <class Type>

void LockPtr<Type>::Commit()

{

delete master_ptr->old_image; master_ptr->old_image = new_image; if (new_image != NULL)

new_image = new Type(*new_image);

}

template <class Type>

Type* LockPtr<Type>::operator->() const

{

if (new_image == NULL)

 

// исключение

 

return new_image;

 

}

 

template <class Type>

 

void LockPtr<Type>::Make()

 

{

 

delete new_image;

// Если new_image не равен NULL

new_image = new Type;

// Новый пустой объект

}

template <class Type>

void LockPtr<Type>::Destroy()

{

delete new_image; new_image = NULL;

соблюдает семантику присваивания, что позволяет вызвать ее для существующего указываемого объекта. При этом объект, на который в данный момент ссылается LockPtr, уничтожается и заменяется новым пустым объектом.

Упрощенное создание объектов

Объекты теперь создаются в три этапа:

1. Создать ConstPtr, указывающий на NULL.

2.Запросить у него Lock.

3.Потребовать у Lock создать объект функцией Make().

Конечно, это произведет впечатление на ваших коллег и лишний раз докажет вашу техническую квалификацию, но… они косо посмотрят на вас и вернутся к оператору new. Ведь он справляется с задачей за один этап, а нас окружают занятые, очень занятые люди. Существует несколько способов свести процесс создания к одному этапу, и самый простой из них — включить в ConstPtr другой конструктор.