Анимация
JavaScript
|
Главная Библионтека return *this; operator Type&() { return current; } void Snapshot() image = current; have image = true; void Commit() { have image = false; } void Ro11back() current = image; have image = false; bool HaveImage() { return have image; } Этот шаблон работает со всеми классами, которые удовлетворяют двум условиям: 1 . Тип, используемый в качестве параметра, имеет конструктор без аргументов. Он используется в конструкторе AutoImage для инициализации current и image. 2. Тип, используемый в качестве параметра, допускает присваивание с помощью оператора = по умолчанию, предоставленного компилятором, или перегруженного варианта для данного типа. Используется в функциях Shapshot() и Ro11back(). Все встроенные типы (такие как int и double) удовлетворяют этим условиям. Подходят и другие классы, имеющие конструктор без аргументов и рабочий оператор =. Чем дольше я имею дело с C++, тем чаще мне кажется, что нарушение этих требований - проявление злостного непрофессионализма, за которое следует наказывать парой лет каторжного программирования на BASIC. Заодно я бы издал закон о том, чтобы конструкторы копий всегда работали так, как им положено. Конструктор копий AutoImage следует примеру ImagePtr и ImageStackPtr - он использует конструктор без аргументов для создания фиктивного объекта image и присваивает have image значение false. Оператор = делает то же самое, однако в нем не удается найти удобный способ уничтожить объект переменной image. Мы выбираем меньшее из двух зол - объект остается без изменений и попросту игнорируется, поскольку переменная have image равна false. Если вас это не устраивает и вы действительно хотите оставить объект image неинициализированным до тех пор, пока в нем не появится настоящий образ, и уничтожить его после присвоения false переменной have image, имеются два возможных решения: 1 . Изменить тип image с Type на Type* и выделять для него память оператором new. Это увеличит накладные расходы по сравнению с автоматическими объектами, однако вы сможете в полной мере контролировать процесс создания и уничтожения. 2. Воспользоваться идиомой «виртуальных конструкторов» из главы 1 3. Не вдаваясь в подробности, скажу, что это позволит вам объявить image чем-то приятным для глаза - например, unsigned char image(sizeof Type) - нежели вызывать конструктор и деструктор Type вручную. Компиляторы С++ недолюбливают подобные фокусы, поэтому, прежде чем пускаться на авантюры, внимательно прочитайте главу 1 3. Если AutoImage будет использоваться только для структур или классов, добавьте оператор ->: Type* operator->() { return ¤t; } Обратите внимание: в отличие от предыдущих версий -> этот оператор не может быть константной функцией, поскольку current находится внутри *this и мы не можем гарантировать, что -> не будет использоваться для обращений к неконстантным функциям current. Следующий класс демонстрирует возможное использование этого шаблона. Вмещающему объекту Foo незачем создавать свой образ, как в предыдущих указателях на объекты, поскольку все его переменные способны поддерживать свои образы по отдельности. class Foo { private: AutoImage<int> some integer; AutoImage<Bar> bar; public: void Ro11back() some i nteger.Rol 1back(); bar.Ro11back(); void Commit() some i nteger.Commit(); bar.Commit(); void Snapshot() some integer.Snapshot(); bar.Snapshot(); int ProvideInt() const { return some integer; } void ChanheInt(int new va1ue) if (!some integer.HaveImage()) some i nteger.Snapshot(); int&(some integer) = new va1ue; const Bar& ProvideBar() const { return bar; } Bar& UpdateBar() if (!bar.HaveImage()) bar.Shapshot(); return Bar&(bar); Предполагается, что Bar соответствует необходимым условиям. Последние четыре функции перед тем, как обновлять переменную, создают «моментальный снимок» объекта. Для int получение копии по определению является константным по отношению к копируемой переменной. При вызове функции, изменяющей значение переменной, настает время делать снимок. Для работы с другой переменной, bar, предоставляется как константная, так и неконстантная функция. Конечно, хотелось бы просто перегрузить функцию ProvideBar(), чтобы одна перегруженная версия возвращала const Bar&, а другая - неконстантный Bar&, но тогда их сигнатуры будут совпадать. Помните: две функции не могут иметь одинаковые имена и аргументы и отличаться только типом возвращаемого значения. Я никогда не понимал этого ограничения С++, которое запрещает создавать константную и неконстантную версию оператора ->: const Type* operator->() const; Снимок не создается Type* operator->() const; Создает снимок Конечно, это намного упростило бы жизнь, но назвать эти загадочные ограничения бесполезными нельзя - они дают знатокам С++ хорошую тему для разговоров на семинарах с коктейлями. Раз уж речь зашла об ограничениях С++, упомяну еще об одном. Взгляните на приведенный выше код класса 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; } 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [ 43 ] 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |