|
Программирование >> Синтаксис инициирования исключений
конкретному типу (например, создание и уничтожение образов). Обе функции после завершения оставляют ConstPtr заблокированным. Создание и уничтожение объектов Пора заполнить кое-какие пробелы. Раз уж наши транзакции достаточно сложны, чтобы для них была оправдана вся эта возня, они наверняка будут создавать или уничтожать объекты. Операции создания и уничтожения также должны быть отменяемыми. Если объект создавался, операция отмены должна его уничтожать, а если уничтожался - возвращать его из мертвых. Для этого придется внести изменения как в класс ConstPtr , так и в класс LockPtr . Мы уже сделали первый шаг в этом направлении, объявив деструктор ConstPtr закрытым, чтобы им могли воспользоваться только ConstPtr или его друзья. Давайте разберемся с оставшимися проблемами. Изменения в классе ConstPtr Несомненно, создание указываемого объекта представляет собой изменение и потому должно осуществляться через LockPtr . Но для того чтобы получить LockPtr , мы должны сначала иметь ConstPtr и его функцию Lock() . Следовательно, только что описанный конструктор ConstPtr работать не будет - он создает уникальный объект перед вызовом Lock() . ConstPtr должен находиться в состоянии NULL до тех пор, пока LockPtr не выделит память под объект и не закрепит эти изменения. В ConstPtr необходимо внести следующие изменения: В конструкторе без аргументов присваивать переменной o1d image значение NULL. Добавить оператор ! , который проверяет, равен ли адрес значению NULL. Инициировать исключение в операторе ->, если адрес равен значению NULL. Либо запретить копирование, либо присвоить копии o1d image значение NULL. Проблема с обычным конструктором копий ConstPtr заключается в том, что он может создать новую копию указываемого объекта, но не позволит отменить ее создание. Ниже приводится новая версия конструктора ConstPtr. Определения функций, не изменившиеся по сравнению с показанной выше упрощенной версией не показаны. private: ConstPtr(const ConstPtr&) : o1d image(NULL), lock(NULL) {} public: ConstPtr() : o1d image(NULL), lock(NULL) {} bool operator!() { return o1d image == NULL; } const Type* operator->() const if (o1d image == NULL) Исключение return o1d image; Изменения в классе LockPtr Отныне LockPtr предстоит выполнять намного больше работы: Он должен при необходимости создавать указываемый объект по требованию. Для этого в него будет добавлена функция Make() . Оператор -> должен инициировать исключение, если адрес равен NULL. Ниже приведены определения только изменившихся функций. В объявлении LockPtr public: void Make(); Создать новый указываемый объект void Destroy(); Уничтожить указываемый объект Изменившиеся определения template <c1ass Type> LockPtr<Type>::LockPtr(Transaction* t, ConstPtr<Type>* cp) : transaction(t), master ptr(cp), new image(cp->o1d image != NULL ? new Type(*(cp->o1d image)) : NULL) template <c1ass Type> void LockPtr<Type>::Commit() delete master ptr->o1d image; master ptr->o1d image = new image; if (new image != NULL) new image = new Type(*new image); template <c1ass Type> Type* LockPtr<Type>::operator->() const if (new image == NULL) исключение return new image; template <c1ass Type> void LockPtr<Type>::Make() delete new image; Если new image не равен NULL new image = new Type; Новый пустой объект template <c1ass Type> void LockPtr<Type>::Destroy() delete new image; new image = NULL; Функция Make() соблюдает семантику присваивания, что позволяет вызвать ее для существующего указываемого объекта. При этом объект, на который в данный момент ссылается LockPtr, уничтожается и заменяется новым пустым объектом. Упрощенное создание объектов Объекты теперь создаются в три этапа: 1. Создать ConstPtr, указывающий на NULL. 2. Запросить у него Lock. 3. Потребовать у Lock создать объект функцией Make(). Конечно, это произведет впечатление на ваших коллег и лишний раз докажет вашу техническую квалификацию, но они косо посмотрят на вас и вернутся к оператору new. Ведь он справляется с задачей за один этап, а нас окружают занятые, очень занятые люди. Существует несколько способов свести процесс создания к одному этапу, и самый простой из них - включить в ConstPtr другой конструктор. ConstPtr<Type>::ConstPtr(Transaction* t) : o1d image(NULL), lock(NULL) LockPtr<Type>& 1p = Lock(t); 1p.Make(); Последующий вызов Lock() возвращает уже созданный LockPtr . Отмена Все до смешного просто. Если вы внимательно следили за тем, как развивается тема транзакций, то для безопасной и универсальной реализации отмены вам потребуется совсем немного - включить необходимые фрагменты из вашей любимой библиотеки классов с графическим интерфейсом пользователя. Для создания и манипуляций со структурами данных транзакций идеально подходят классы событий. Объект-событие создается для выполнения некоторой команды или внесения изменений. Например, пользователь выбирает команду Delete в меню Edit или нажимает клавишу Delete на клавиатуре. Объект создается на базе класса, который прекрасно разбирается в удалениях. В дальнейшем этот объект не только вносит затребованные изменения, но и обеспечивает работу команды Undo меню Edit. Создайте транзакцию в его конструкторе, воспользуйтесь транзакцией для сохранения всех изменений, а затем - для поддержки отмены. Варианты Мы не будем пережовывать дополнительный код, относящийся к транзакциям. Если вы еще не уловили общий принцип, бессмысленно подливать масло в огонь, а если уловили, то без труда реализуете все, о чем говорится ниже. Так что я ограничусь лишь замечаниями, в которых описываются некоторые важные вариации на данную тему. Вложенные блокировки Чтобы вся методика имела хоть какой-то смысл, мы должны спрятать ключи от указываемых объектов и заставить всех работать с ContrPtr и LockPtr. Это относится не только к изменениям, вносимым в указываемые объекты непосредственно клиентами, но и к тем, которые один указываемый объект вносит в другой. Таким образом, в общем случае указываемые объекты должны обращаться друг к другу через ConstPtr и LockPtr, как и клиенты. class A { private: ConstPtr<B>& b; ConstPtr, как выше И т.д. Если экземпляр класса A захочет вызвать неконстантную функцию b, он должен сначала получить LockPtr для b. Таким образом, класс A должен знать, какую транзакцию он выполняет при попытке изменения b, и эта информация должна поступить от клиента. Для простоты назовем объекты, к которым клиенты обращаются напрямую, первичными, а объекты, косвенно обновляемые указываемым объектом, - вторичными по отношению к указываемому объекту (один и тот же объект может одновременно быть и первичным, и вторичным). Если вторичный объект инкапсулируется в первичном (то есть вторичный объект невидим для окружающего мира), это правило можно обойти. Конструктор копий первичного объекта должен продублировать вторичный объект, а оператор = первичного объекта должен продублировать вторичный объект правостороннего выражения. При соблюдении этих условий логика создания образов в ConstPtr и LockPtr будет правильно работать с инкапсулированными объектами. Это происходит автоматически в тех случаях, когда вторичный объект внедряется в первичный (то есть когда в каждом экземпляре A объект B хранится не как указатель, а как обычная переменная класса): class A { private:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |