|
Образы автоматических объектов Концепцию образа можно немного обобщить, чтобы она распространялась не только на объекты, созданные оператором new и обслуживаемые *-указателями, но и автоматические объекты. Автоматическими считаются стековые объекты, а также переменные и компоненты базовых классов вмещающего объекта независимо от того, выделялась память под вмещающий объект динамически или нет. template <class Type> class AutoImage { private: Type current; Type image; bool have_image; // Истина, если образ существует public: AutoImage() : have_image(false) {} AutoImage(const AutoImage<Type>& ai) : current(ai.current), image(), have_image(false) {} AutoImage<Type>& operator=(const AutoImage<Type>& ip) { if (this != &ip) { current = ip.current; have_image = false; } return *this; } AutoImage<Type>& operator=(const Type& t) { current = t; return *this; } operator Type&() { return current; } void Snapshot() { image = current; have_image = true; } void Commit() { have_image = false; } void Rollback() { current = image; have_image = false; } bool HaveImage() { return have_image; } }; Этот шаблон работает со всеми классами, которые удовлетворяют двум условиям: 1. Тип, используемый в качестве параметра, имеет конструктор без аргументов. Он используется в конструкторе AutoImage для инициализации current и image. 2. Тип, используемый в качестве параметра, допускает присваивание с помощью оператора = по умолчанию, предоставленного компилятором, или перегруженного варианта для данного типа. Используется в функциях Shapshot() и Rollback(). Все встроенные типы (такие как 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. Воспользоваться идиомой «виртуальных конструкторов» из главы 13. Не вдаваясь в подробности, скажу, что это позволит вам объявить image чем-то приятным для глаза - например, unsigned char image(sizeof Type) - нежели вызывать конструктор и деструктор Type вручную. Компиляторы С++ недолюбливают подобные фокусы, поэтому, прежде чем пускаться на авантюры, внимательно прочитайте главу 13. Если AutoImage будет использоваться только для структур или классов, добавьте оператор ->: Type* operator->() { return ¤t; } Обратите внимание: в отличие от предыдущих версий -> этот оператор не может быть константной функцией, поскольку current находится внутри *this и мы не можем гарантировать, что -> не будет использоваться для обращений к неконстантным функциям current. Следующий класс демонстрирует возможное использование этого шаблона. Вмещающему объекту Foo незачем создавать свой образ, как в предыдущих указателях на объекты, поскольку все его переменные способны поддерживать свои образы по отдельности. class Foo { private: AutoImage<int> some_integer; AutoImage<Bar> bar; public: void Rollback() { some_integer.Rollback(); bar.Rollback(); } void Commit() { some_integer.Commit(); bar.Commit(); } void Snapshot() { some_integer.Snapshot(); bar.Snapshot(); } int ProvideInt() const { return some_integer; } void ChanheInt(int new_value) { if (!some_integer.HaveImage()) some_integer.Snapshot(); int&(some_integer) = new_value; } 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() для каждой переменной класса». Компилятор знает, как составить список такого рода (и использует его в конструкторах), но вам ни за что не скажет. |
Copyright 2005. Климов Александр. All Right Reserved.