[ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ]



Ответов: 0
25-02-12 07:01







   Web - программирование
PHP


ASP






XML



CSS

SSI





   Программирование под ОС











   Web - технологии








   Базы Данных









   Графика






Данные




Программирование под ОС / C - C++ /

Рассуждения на тему Умных указателей

Когда я занимался изучением С++, то не раз встречался с "умными" указателями. Они встречались везде и все время в разных вариантах. Без стандартизации.

Вообще, мысль об упрощении себе жизни вертится в головах программистов всегда - "Лень - двигатель прогресса". Поэтому и были придуманы не просто указатели, а такие из них, которые брали часть умственного напряга на себя, тем самым, делая вид, что они нужны.

Для тех, кто еще не знает про хоть какие-нибудь разновидности данного творения (собственно я опять об указателях), можно сказать пару слов прямо с самого начала. А если вы уже знаете в чем собака (или где?) зарыта, то пропустите пару тройку обзацев (я надеюсь, что Страуструпа все читали).

Итак, что такое SmartPointer-ы? По сути это такие классы, которые умеют чуть больше ... - а в общем, смотрим пример (я только так могу понять какую-нибудь писанину на программисткую тему :):

class A

{ 

  private:

    int count;

  public:

    A(){count = 0;}

    void addref(){count++;}

    void release(){if(--count == 0) delete this;}

  protected:

    ~A();

  public:

    void do_something(){cout << "Hello";}

};

Сначала придумали внутри объекта считать ссылки на него из других объектов при помощи "механизма подсчета ссылок". Суть здесь в том, что когда вы сохраняете ссылку на объект, то должны вызвать для него addref, а когда избавляетесь от объекта, то вызвать release. Сложно? Совсем нет - это дело привычки. Таким образом, объект умеет сам себя удалять. Здорово? Так оно и есть.

Кстати такой объект может существовать только в куче, поскольку деструктор в "защищенной" зоне и по той же причине нельзя самому сделать "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. Вот это уже что-то стоит!

Дальше больше, можно добавить всякие нужные штучки, типа оператора присваивания, еще одного селектора (или как его некоторые называвют "разъименователь" указателя) и так далее. Таким образом получится удобная вещь (конечно, если все это дело завернуть в шаблон :).

Вот, наверное, и хватит для вступления.

Теперь немного сменим направление рассуждений. Прочитав книжку Элджера, я узнал о более тонких решениях, чем просто SmartPointer. Оказывается существуют такие вещи, как "Мудрые указатели", "Ведущие указатели", "Гениальные указатели", "Грани", "Кристаллы" - в общем, хватает всякого добра. Правда, в практичности этих поделок я усомнился, несмотря на их "изящество". То есть, конечно, они полезны, но не являются, своего рода, панацеей (кроме "ведущие" указателей).

В общем на меня сильно подействовали только "ведущие" и "умные" указатели, причем так, что захотелось сделать нечто похожее, но уже в исходниках и также реализовать это на шаблонах.

Что из этого получилось, можете судить по остальной части текста...

Все научные изыскания проходят сверху вниз, а их преподавание - снизу вверх. Не будем отходить от этого правила :)).

Начнем с такого класса как Countable, который будет отвечать за подсчет чего либо.

Итак он выглядит примерно так (в дальнейшем будем опускать реализации многих функций из-за их тривиальности, оставляя, тем самым, только их объявления :)

class Countable

{

  private:

    int count;

  public:

    int increment ();

    int decrement ();

};

Здесь особо нечего говорить, кроме того, что, как всегда, этот класс можно сделать более "удобным", добавив такие вещи, как поддержку режима многопоточности и т.д.

Следующий простой класс прямо вытекает из многопоточности и осуществляет поддержку этого режима для своих детей :):

class Lockable

{

  public:

    void lock();

    void unlock();

    bool islocked();

};

Этот класс не вносит никакой новизны, но стандартизует поддержку многопоточности при использовании, например, различных платформ.

Теперь займемся собственно указателяли:

class AutoDestroyable : public Countable

{

  public:

    virtual int addref ();

    virtual int release ();

  protected: 

    virtual ~AutoDestroyable();

    ...

};

Из кода видно, что этот класс занимается подсчетом ссылок и "убивает" себя если "пришло время".

А сейчас процитируем код "умного" указателя, для того чтобы синхронизовать наши с вами понимание этого чуда.

template 

class SP

{

  private:

    T* m_pObject;

  public:

    SP ( T* pObject){ m_pObject = pObject; }

    ~SP () { if( m_pObject ) m_pObject->release (); }

    T* operator -> ();

    T& operator * ();

    operator T* ();

    ...

};

Это уже шаблон, так зависит от объекта класса, к которому применяется. Задачу "умного" указателя я пояснил выше и в итоге при сравнении с ситуацией без его использования положителен, только тем, что для объекта, создаваемого в куче, не надо вызывать оператор delete - он сам вызовется, когда это понадобится (что-то мне это напоминает Java...).

Теперь остановимся на минутку и подумаем, когда мы можем использовать этот тип указателей, а когда нет. Главное требование со стороны SP, это умение основного объекта вести подсчет ссылок на себя и уничтожать себя в тот момент, когда не становится нужен. Это серьезное ограничение, поскольку не во все используемые классы вы сможете добавить эти возможности. Вот несколько причин, по которым вы не хотели бы этого (или не можете):

Вы используете закрытую библиотеку (уже скомпилированную) и физически не можете добавить кусок кода в нее.

Вы используете открытую библиотеку (с исходными кодами), но не хотите изменять ее как-либо, потому что все время меняете ее на более свежую (кто-то ее делает и продвигает за вас).

И, наконец, вы используете написанный вами класс, но не хотите по каким-либо причинам вставлять поддержку механизма подсчета ссылок.

Итак, причин много или по крайней мере достаточно для того, чтобы задуматься над более универсальным исполнением SP. Ладно, давайте заглянем в книгу Элджера... Что он нам может предложить?... Ага, вот - "ведущие" указатели и "дескрипторы" (новые словечки, не правда ли? - нет? ну и ладно). Посмотрим на схематичный код этих классов:

template 

clacc MP : public AutoDestroyable

{

  private:

    T* m_pObj;

  public:

    MP(T* p);

    T* operator ->();

  protected:

    operator T*();

};



template 

class H

{

  private:

    MP* m_pMP;

  public:

    H(T*);

    MP& operator T->();

    bool operator ==(H&);

    H operator =(H&);

    ...

};

Что мы видим на этот раз? А вот что. MP - это "ведущий" указатель, т.е. такой класс, который держит в себе объект основного класса и не дает его наружу. Появляется только вместе с ним и умирает аналогично. Его главная цель, это реализовывать механизм подсчета ссылок.

В свою очередь H, это класс очень похожий на SP, но общяется не с объектом основного класса, а с соответствующим ему MP.

Результат этого шаманства очевиден - мы можем использовать механизм "умных" указателей для классов не добавляя лишнего кода в их реализацию.

По моему здорово! И это действительно так, ведь широта использования такого подхода резко увеличивается и уже папахивает панацеей.

Так я рассуждал, прочтя книгу Элджера (точнее ту часть книги, которая посвящена таким указателям классов). Но в один прекрасный (а может он и не перкоасный) момент, я, вдруг на свою голову, подумал о двух жутких вещах: многопоточности и множественном наследовании.

Что мы имеем, используя классы MP и H? - нет поддержки этих двух вещей. И если с первой все понятно (нужно наследовать MP от Lockable), то со вторым сложнее.

Итак, посвятим немного бумаги (или места на диске) описанию еще одного типа указателей, которые призваны "решить" проблему множественного наследования (а точнее полиморфизма).

Рассмотрим классы PP и TP:

class PP : public AutoDestroyable

{};



template

class TP

{

  protected:

    T* m_pObject;

    PP* m_pCounter;



  public:

    TP ();

    TP ( T* pObject );

    TP& operator = ( TP& tp );

    T* operator -> ();

    T& operator * ();

    operator T* ();

    bool operator == ( TP& tp );

};

Класс PP является "фиктивным ребенком" AutoDestroyable и вы не забивайте себе этим голову. А вот класс TP можно посмотреть и попристальнее.

Схема связей в этом случае выглядит уже не H->MP->Obj, а PP<-TP->Obj, т.е. Счетчик ссылок (а в данном случае, это PP) никак не связан с основным объектом или каким-либо другим и занимается только своим делом - ссылками. Таким образом, на класс TP ложится двойная обязанность: выглядеть как обычный указатель и отслеживать вспомогательные моменты, которые связаны со ссылками на объект.

Как же нам теперь использовать полиморфизм? ведь мы хотели сделать что-то вроде :

class A : public B

...

TP a;

TP b;

a = new B;

b = (B*)a;

...

Для этого реализуем следующую функцию (и внесем небольшие изменения в класс TP для ее поддержки)

template 

TP smart_cast ( TP& tpin );

(Саму реализацию этой функции я не привожу, поскольку она содержится в архиве.)

Итак, теперь можно написать что-то вроде (и даже будет работать):

class A : public B

...

TP a;

TP b;

a = new B;

b = smart_cast(a);



// или если вы используете Visual C++, то даже 

b = smart_cast(a);

...

Вам ничего не напоминает? Ага, схема таже, что и при использовании static_cast и dynamic_cast. Так как схожесть очень убедительна, можно заключить, что такое решение проблемы более чем изящьно.




Комментарии

 Ваш комментарий к данному материалу будет интересен нам и нашим читателям!



Последние статьи: Программирование под ОС / C - C++ /

Пишем CD проигрыватель 2
28-05-2010   

Сейчас я покажу как можно написать простой проигрыватель CD дисков. Для начала разместим все нужные компоненты на форме (см. рисунок)... подробнее

Кол. просмотров: общее - 4145 сегодня - 0

Пишем браузер
28-05-2010   

Продолжаю тему клонирования программ darkamstera на Delphi в С++Builder. В этой статье я покажу, как с помощью стандартных компонентов, можно создать свой браузер. Браузер будет на движке всеми-любимого InternetExplorer. Наш зверь сможет ходить по URL... подробнее

Кол. просмотров: общее - 2920 сегодня - 0

Информация о системе
28-05-2010   

Выводим информацию о нашей родненькой системе. Для получения большей части информации мы будем использовать обширные функции, это не так сложно, как может показатся с первого взгляда и вообще код довольно простой и примитивный, так что покапавшись в нем пару минут - можно в легкостью разобраться что к чему, зачем и как... подробнее

Кол. просмотров: общее - 2941 сегодня - 0

FTP клиент своими руками
28-05-2010   

Здраствуй, сечас я покажу, как в CBuilder можно создать свой простой FTP-клиент, похожий пример можно найти в документации C++Builder Developnets Guilde... подробнее

Кол. просмотров: общее - 3387 сегодня - 0

Работаем с POP-сервером
28-05-2010   

Здраствуй, в этой статье я расскажу про способы получения e-mail писем и их прочтения. Для начала давайте составим интерфейс будущей программы Вот, что у меня вышло... подробнее

Кол. просмотров: общее - 2985 сегодня - 0



  WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects