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

145

void Rollback()

{

if (have_image) { current = image; have_image = false;

}

bool HaveImage() { return have_image; }

};

Если вам захочется еще немного автоматизировать этот шаблон, добавьте вызовы Snapshot() в обе операторные функции operator=().

Указателей расплодилось слишком много, и терминология начинает запутываться. Термин «указатель образов» обозначает указатель, в котором содержится несколько образов объекта (и который почти всегда является ведущим), тогда как термин «образы указателей» относится к классу наподобие показанного выше, в котором хранится несколько предыдущих значений самого указателя.

Комбинации и вариации

В нашей коллекции скопилось уже немало «строительных блоков», а возможности их комбинирования безграничны. Выберите по одному варианту в каждой позиции, и вы получите некое специализированное решение для работы с образами:

простые/ведущие указатели;

образы автоматические/созданные оператором new;

один образ/стеки образов;

образы объектов/ образы указателей;

спонтанное создание образа/ ручной вызов функции Snapshot().

Приведенный список ни в коем случае не исчерпывает всех возможных вариантов. Скорее это представительный перечень концепций, сосредоточенных вокруг общей темы — управления образами. Все концепции ориентированы на С++ с его уникальным синтаксисом и семантикой (в частности, конструкторами и операторами). Пошевелите мозгами и подумайте, как эти концепции подходят к вашей конкретной задаче. Именно этим мы и займемся в оставшейся части этой главы.

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

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

template <class Type>

 

class ImagePtr {

 

private:

 

Type* current;

// Текущий образ, предоставляемый компоненту

Type* undo;

// Предыдущий образ

public:

 

ImagePtr();

 

ImagePtr(const ImagesPtr<Type>& ip); ~ImagePtr();

ImagePtr<Type>& operator=(const ImagePtr<Type>& ip); void Snapshot();

void Commit();

146

void Rollback();

Type* operator->() const;

};

Для мира транзакций придется внести ряд изменений:

Объект в любой момент времени может быть заблокирован не более чем одной транзакцией.

Объект не может быть изменен при снятой блокировке.

Заблокировнный объект может быть измене лишь объектом, который принадлежит транзакции, установившей блокировку.

Следовательно, нам придется создать некоторое представление для транзакции, а заодно — поразмять мышцы С++ и построить соответствующую семантику. Транзакции будут представлены классом Transaction. Для блокировок мы воспользуемся специальным обновляющим указателем. Иначе говоря, обычные клиенты работают с умным указателем, не допускающим обновления, а клиенты транзакции-владельца получают доступ к другому указателю с возможностью обновления. Ниже приведена прямолинейная (хотя необязательно самая эффективная) реализация этой архитектуры. Позднее мы снимем эти упрощающие ограничения и расширим архитектуру:

1. Нас интересует только отмена изменений в существующих объектах, а не отмена создания и уничтожения объектов в процессе транзакции.

2.Вопрос о том, когда именно должна устанавливаться блокировка объекта выходит за рамки описанной упрощенной архитектуры.

Транзакции и блокировки

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

// В файле

Transaction.h

 

class Lock

{

 

friend class Transaction;

 

protected:

 

 

Transaction* transaction;

// Транзакция, которой принадлежит this

Lock() : transaction(NULL) {} void RegisterLock(Transaction* t)

{

if (transaction != NULL) {

// Конфликт – уже имеется другой владелец

cerr << “Lock::RegisterLock – already locked” << endl;

}

else { t->AddLock(this); transaction = t;

}

}

virtual ~Lock() {}

virtual void Rollback() = 0; virtual void Commit() = 0;

};

 

class Transaction {

 

friend class Lock;

// Чтобы предоставить доступ к AddLock()

147

private:

 

SafeSet<Lock>* locks;

 

void AddLock(Lock*);

// Включить блокировку в транзакцию

public:

 

~Transaction();

 

void Commit();

// Закрепить все образы

void Rollback();

// Отменить все образы

bool OwnsLock(Lock*);

// Истина, если блокировка

 

// принадлежит транзакции

};

 

Класс Transaction поддерживает коллекцию блокировок с помощью гипотетического шаблона Collection. Функция RegisterLock() включена в базовый класс Lock и потому может обратиться к закрытой функции AddLock() класса Transaction. Дружба не наследуется, поэтому объявление другом класса Lock не делает друзьями его производные классы. Реализации выглядят довольно просто.

void Transaction::AddLock(Lock* lock)

{

 

*locks += lock;

// Использует перегрузку += для коллекции

}

void Transaction::Commit()

{

SafeSetIterator<Lock>* iter = locks->Iterator(); while (iter->More())

iter->Next()->Commit(); delete iter;

}

void Transaction::Rollback()

{

SafeSetIterator<Lock>* iter = locks->Iterator(); while (iter->More())

iter->Next()->Rollback(); delete iter;

}

bool Transaction::OwnsLock(Lock* lock)

{

return *locks >= lock;

}

Предполагается, что шаблон Collection содержит функцию DeleteAll() для удаления всех объектов; что перегруженный оператор += (операторная функция operator+=(Type*)) включает элемент в коллекцию; что перегруженный оператор >= определяет принадлежность к коллекции, а функция Iterator() возвращает вложенный итератор. Это обобщенные условия; используйте те, которые действуют в вашем случае.

Класс ConstPtr

Классы, производные от Lock, должны ссылаться на нечто близкое к указателям, доступным только для чтения, с которыми на страницах книги вы уже познакомились.

template <class Type>

class LockPtr;

// Ссылка вперед на класс, производный от Lock

148

template <class Type>

 

class ConstPtr {

 

friend class LockPtr<Type>;

 

private:

 

Type* old_image;

// Образ перед транзакцией

LockPtr<Type>* lock;

// Текущая блокировка, если она есть

~ConstPtr() { delete old_image; }

ConstPtr<Type>& operator=(const ConstPtr<Type>& cp)

{ return *this; }

// Присваивание не разрешается

public:

ConstPtr() : old_image(NULL), lock(NULL) {} ConstPtr(const ConstPtr<Type>& cp)

: old_image(new Type(*(cp.old_image))), lock(NULL) {} const Type* operator->() const { return old_image; } LockPtr<Type>& Lock(Transaction* t);

};

template <class Type>

LockPtr<Type>& ConstPtr<Type>::Lock(Transaction* t)

{

if (lock != NULL && !t->OwnsLock(lock))

// Конфликт – уже имеется другой владелец else {

lock = new LockPtr<Type>(t, this); return *lock;

}

}

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

Конфликт в функции Lock можно обработать разными способами:

Инициировать исключение.

Изменить интерфейс и возвращать вместе с блокировкой флаг, показывающий, успешно ли прошла блокировка.

Возвращать LockPtr<Type>* со значением NULL, свидетельствующем о неудачной блокировке.

Возвращать конфликтную блокировку с перегруженным оператором !, с помощью которого можно проверить правильность блокировки.

Предоставить отдельную функцию CanLock(Transaction*), которая возвращает логическое значение.

Выбор зависит от стиля. Вариант с исключением не так уж очевиден; неудача при блокировке представляет собой вполне разумный исход.