|
Программирование >> Синтаксис инициирования исключений
Конечно, это намного упростило бы жизнь, но назвать эти загадочные ограничения бесполезными нельзя - они дают знатокам С++ хорошую тему для разговоров на семинарах с коктейлями. Раз уж речь зашла об ограничениях С++, упомяну еще об одном. Взгляните на приведенный выше код класса Foo. Работа некоторых его функций сводится к вызову одной и той же функции для всех переменных класса и в более общем случае - базовых классов. Скажем, Foo::Commit() просто вызывает Commit() для всех переменных. Весь повторяющийся код приходится писать вручную; в языке сильно не хватает макросредств, которые бы позволяли сказать: Вызвать функцию Commit() для каждой переменной класса . Компилятор знает, как составить список такого рода (и использует его в конструкторах), но вам ни за что не скажет. Образы указателей У шабона AutoImage есть одно довольно занятное применение - им можно воспользоваться для создания образов *-указателя. В некоторых ситуациях не хочется создавать лишние копии указываемого объекта только чтобы следить за тем, на что ссылался указатель в прошлой жизни. Собственно, дело обстоит так каждый раз, когда указатель не является ведущим. Указатель также помогает следить за объектами, которые были созданы или уничтожены в процессе транзакции. AutoImage<Foo*> f; Теперь вы можете восстановить состояние указателя f в начале транзакции, в том числе и NULL. Тем не менее, существует веский довод в пользу создания специализированного шаблона для *-указателей - необходимость перегрузки оператора ->, чтобы указатель образов можно было использовать в левой части выражений (что-нибудь типа ptr->MemberOfPointer();). Для *-указателей AutoImage похож на глупые указатели, с которыми мы рассправились в начале главы 5. Следующий шаблон больше напоминает обычные умные (но не ведущие!) указатели. template <c1ass Type> class PtrImage { private: Type* current; Type* image; bool have image; Истина, если образ существует public: PtrImage() : current(NULL), image(NULL), have image(fa1se) {} PtrImage(const PtrImage<Type>& pi) : current(pi.current), image(NULL), have image(fa1se) {} PtrImage<Type>& operator=(const PtrImage<Type>& pi) if (this != &pi) current = pi.current; return *this; PtrImage<Type>& operator=(Type* t) { current = t; return *this; } operator Type*() { return current; } Type* operator->() const { return current; } bool operator!() { return current == NULL; } void Snapshot() image = current; have image = true; void Commit() { image = NULL; have image = false; } void Ro11back() if (have image) { current = image; have image = false; bool HaveImage() { return have image; } Если вам захочется еще немного автоматизировать этот шаблон, добавьте вызовы Snapshot() в обе операторные функции operator=() . Указателей расплодилось слишком много, и терминология начинает запутываться. Термин указатель образов обозначает указатель, в котором содержится несколько образов объекта (и который почти всегда является ведущим), тогда как термин образы указателей относится к классу наподобие показанного выше, в котором хранится несколько предыдущих значений самого указателя. Комбинации и вариации В нашей коллекции скопилось уже немало строительных блоков , а возможности их комбинирования безграничны. Выберите по одному варианту в каждой позиции, и вы получите некое специализированное решение для работы с образами: простые/ведущие указатели; образы автоматические/созданные оператором new; один образ/стеки образов; образы объектов/ образы указателей; спонтанное создание образа/ ручной вызов функции Snapshot(). Приведенный список ни в коем случае не исчерпывает всех возможных вариантов. Скорее это представительный перечень концепций, сосредоточенных вокруг общей темы - управления образами. Все концепции ориентированы на С++ с его уникальным синтаксисом и семантикой (в частности, конструкторами и операторами). Пошевелите мозгами и подумайте, как эти концепции подходят к вашей конкретной задаче. Именно этим мы и займемся в оставшейся части этой главы. Транзакции и отмена Решение проблемы транзакций в значительной степени связано с проблемами отмены и многопоточных итераторов, поэтому сначала мы поговорим о транзакциях. Итак, мы хотим предотвратить обновление некоторого объекта более чем одной транзакцией. Похоже, в решении этой задачи нам помогут указатели образов. Давайте посмотрим свежим взглядом на обобщенный указатель образов, приведенный в начале главы: template <c1ass 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(); void Ro11back(); 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 Ro11back() = 0; virtual void Commit() = 0; class Transaction { friend class Lock; Чтобы предоставить доступ к AddLock()
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.066
При копировании материалов приветствуются ссылки. |