|
Конструирование Предположим, вы разрешили пользователям создавать собственные указываемые объекты и передавать их ведущим указателям во время конструирования. template <class Type> class MP { private: Type* t; public: MP() : t(NULL) {} MP(Type* pointer) : t(pointer) {} ~MP() { delete t; } // И т.д. }; Foo* foo = new Foo; MP<Foo> mp1(foo); MP<Foo> mp2(foo); // Облом! Насколько проще была бы жизнь без таких пользователей! Когда mp1 удаляется, пропадает и foo. В итоге mp2 начинает указывать неизвестно куда. «Зачем кому-нибудь потребуется вытворять такое?» - спросите вы. Как говорилось в одном рекламном ролике: «Зачем спрашивать «Зачем»?» Если вы оставите такую дыру, можете не сомневаться: кто-нибудь когда-нибудь изобретет дьявольский план, использует ее и обвинит во всех смертных грехах вашу программу. Пользователь прямо-таки кричит: «Держите меня, пока я не натворил бед». Для этого существует надежный способ: отобрать у него ключи от конструкторов класса указываемого объекта. class Foo { friend class MP<Foo>; protected: Foo(); // Теперь доступ к конструктору имеет только MP<Foo> public: // Оставшаяся часть интерфейса }; template <class Type> class MP { private: Type* t; public: MP() : t(new Type) {} // И т.д. }; Aгa, уже лучше. При создании указателя его конструктор также конструирует и указываемый объект. Объявляя указатель другом, мы можем сделать конструкторы Foo закрытыми или защищенными и сохранить доступ к ним из конструкторов указателя. Теперь клиент никак не сможет добраться до Foo, кроме как через MP<Foo>. Мы еще неоднократно вернемся к вопросу о том, как, когда и где создавать указываемые объекты, а пока давайте немного отвлечемся. Если конструкторы Foo вызываются с аргументами, существуют две альтернативы: 1. Вместо того чтобы пользоваться универсальным шаблоном ведущего указателя, создайте для Foo нестандартный класс ведущего указателя MPFoo. Для каждого конструктора Foo создайте конструктор MPFoo с точно такой же сигнатурой и передайте его аргументы конструктору Foo. 2. Воспользуйтесь безаргументным конструктором для создания объекта и предоставьте отдельную функцию инициализации, которая может вызываться клиентом после конструирования. Второй вариант выглядит так: class Foo { friend class MP<Foo>; protected: Foo(); // Единственный конструктор public: Initialized(int, char*); // Оставшаяся часть интерфейса }; MP<Foo> mpf; mpf->Initialize(17, "Hello"); // Завершить конструирование Такое решение выглядит довольно неуклюже, но оно позволяет работать с универсальным шаблоном ведущего указателя. Существуют и другие причины для использования инициализирующих функций, о которых будет рассказано в следующих главах. Любой из этих вариантов вполне приемлем для решения наших текущих задач. |
Copyright 2005. Климов Александр. All Right Reserved.