|
Программирование >> Арифметические и логические операции
Совсем нет - это дело привычки. Таким образом, объект умеет сам себя удалять. Здорово? Так оно и есть. Кстати такой объект может существовать только в куче, поскольку деструктор в защищенной зоне и по той же причине нельзя самому сделать delete a обойдя release. Теперь переходим к собственно самим умным указателям и опять пример: class PA private: A* pa; public: PA(A* p){pa = p; p->addref();} ~PA(){pa->release();} A* operator ->(){return pa;} Что мы видим? Есть класс PA, который умеет принимать нормальный указатель на объект класса A и выдавать его же по обращению к селектору членов класса. Ну и что? - скажете вы, - Как это может помочь? . За помощью обратимся к двум примерам, которые иллюстрирует эффективность использования класса PA: A* pa = new A(); pa->addref(); pa->do something(); pa->release(); PA pa = new A(); pa->do something(); Посмотрим внимательнее на эти два отрывка... Что видим? Видим экономию двух строчек кода. Здесь вы наверное скажете: И что, ради этого мы столько старались? . Но это не так, потому что с введением класса PA мы переложили на него все неприятности со своевременными вызовами addref и release для класса A. Вот это уже что-то стоит! Дальше больше, можно добавить всякие нужные штучки, типа оператора присваивания, еще одного селектора (или как его некоторые называют разименователь указателя) и так далее. Таким образом получится удобная вещь (конечно, если все это дело завернуть в шаблон. Теперь немного сменим направление рассуждений. Оказывается существуют такие вещи, как Мудрые указатели , Ведущие указатели , Гениальные указатели , Грани , Кристаллы - в общем, хватает всякого добра. Правда, в практичности этих поделок можно усомниться, несмотря на их изящество . То есть, конечно, они полезны, но не являются, своего рода, панацеей (кроме ведущих указателей). В общем особую роль играют только ведущие и умные указа- тели. Начнем с такого класса как Countable, который будет отвечать за подсчет чего либо. Итак он выглядит примерно так (в дальнейшем будем опускать реализации многих функций из-за их тривиальности, оставляя, тем самым, только их объявления: class Countable private: int count; public: int increment (); int decrement (); Здесь особо нечего говорить, кроме того, что, как всегда, этот класс можно сделать более удобным , добавив такие вещи, как поддержку режима многопоточности и т.д. Следующий простой класс прямо вытекает из многопоточности и осуществляет поддержку этого режима для своих детей: class Lockable public: void unlock(); bool islocked(); Этот класс не вносит никакой новизны, но стандартизует поддержку многопоточности при использовании, например, различных платформ. Теперь займемся собственно указателями: class AutoDestroyable : public Countable public: virtual int addref (); virtual int release (); protected: virtual ~AutoDestroyable(); Из кода видно, что этот класс занимается подсчетом ссылок и убивает себя если пришло время . А сейчас процитируем код умного указателя, для того чтобы синхронизовать наши с вами понимание этого чуда. template <class T> class SP private: T* m pOb]ect; public: SP ( T* pOb]ect){ m pOb]ect = pObject; } ~SP () { if( m pOb]ect ) m pOb]ect->release (); } T* operator -> (); T& operator * (); operator T* (); Это уже шаблон, он зависит от объекта класса, к которому применяется. Задача умного указателя была рассмотрена выше и итог при сравнении с ситуацией без его использования положителен только тем, что для объекта, создаваемого в куче, не надо вызывать оператор delete - он сам вызовется, когда это понадобится. Теперь остановимся на минутку и подумаем, когда мы можем использовать этот тип указателей, а когда нет. Главное требование со стороны SP, это умение основного объекта вести подсчет ссылок на себя и уничтожать себя в тот момент, когда он не становится нужен. Это серьезное ограничение, поскольку не во все используемые классы вы сможете добавить эти возможности. Вот несколько причин, по которым вы не хотели бы этого (или не можете): ♦ Вы используете закрытую библиотеку (уже скомпилированную) и физически не можете добавить кусок кода в нее. ♦ Вы используете открытую библиотеку (с исходными кодами), но не хотите изменять ее как-либо, потому что все время меняете ее на более свежую (кто-то ее делает и продвигает за вас). ♦ И, наконец, вы используете написанный вами класс, но не хотите по каким-либо причинам вставлять поддержку механизма подсчета ссылок. Итак, причин много или по крайней мере достаточно для того, чтобы задуматься над более универсальным исполнением SP. Посмотрим на схематичный код ведущих указателей и дескрипторов : template <class T> clacc MP : public AutoDestroyable private: T* m pOb]; public: MP(T* p); T* operator ->(); protected: operator T*(); template <class T> class H private: MP<T>* m pMP; public: H(T*); MP& operator T->(); bool operator ==(H<T>&); H operator =(H<T>&); Что мы видим на этот раз? А вот что. MP - это ведущий указатель, т.е. такой класс, который держит в себе объект основного класса и не дает его наружу. Появляется только вместе с ним и умирает аналогично. Его главная цель, это реализовывать механизм подсчета ссылок. В свою очередь H - это класс очень похожий на SP, но общается не с объектом основного класса, а с соответствующим ему MP. Результат этого шаманства очевиден - мы можем использовать механизм умных указателей для классов не добавляя лишнего кода в их реализацию. И это действительно так, ведь широта использования такого подхода резко увеличивается и уже попахивает панацеей. Что же можно сказать о многопоточности и множественном наследовании: при использовании классов MP и H - нет поддержки этих двух вещей. И если с первой все понятно (нужно наследовать MP от Lockable), то со вторым сложнее. Итак, посвятим немного времени описанию еще одного типа указателей, которые призваны решить проблему множественного наследования (а точнее полиморфизма). Рассмотрим классы PP и TP: class PP : public AutoDestroyable template<class T> class TP protected: T* m pOb]ect; PP* m pCounter; public: TP (); TP ( T* pObject ); TP<T>& operator = ( TP<T>& tp ); T* operator -> (); T& operator * (); operator T* (); bool operator == ( TP<T>& tp ); Класс PP является фиктивным ребенком AutoDestroyable и вы не забивайте себе этим голову. А вот класс TP можно посмотреть и попристальнее. Схема связей в этом случае выглядит уже не H->MP->Obj, а PP<-TP->Obj, т.е. Счетчик ссылок (а в данном случае, это PP) никак не связан с основным объектом или каким-либо другим и занимается только своим делом - ссылками. Таким образом, на класс TP ложится двойная обязанность: выглядеть как обычный указатель и отслеживать вспомогательные моменты, которые связаны со ссылками на объект. Как же нам теперь использовать полиморфизм? Ведь мы хотели сделать что-то вроде: class A : public B TP<A> a; TP<B> b; a = new B; b = (B*)a; Для этого реализуем следующую функцию (и внесем небольшие изменения в класс TP для ее поддержки): template <class T, class TT> TP<T> smart cast ( TP<TT>& tpin ); Итак, теперь можно написать что-то вроде (и даже будет работать): class A : public B TP<A> a; TP<B> b; a = new B; b = smart cast<B, A>(a); или если вы используете Visual C++, то даже b = smart cast<B>(a); Вам ничего не напоминает? Ага, схема та же, что и при использовании static cast и dynamic cast. Так как схожесть очень убедительна, можно заключить, что такое решение проблемы более чем изящно. Глава 7. Виртуальные деструкторы В практически любой мало-мальски толковой книге по С++ рассказывается, зачем нужны виртуальные деструкторы и почему их надо
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |