Міркування на тему "Розумних" покажчиків

Євген Моісеєв

Коли я займався вивченням С + +, то не раз зустрічався з "розумними" покажчиками. Вони зустрічалися скрізь і весь час в різних варіантах. Без стандартизації.

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

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

Отже, що таке 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. Так як схожість дуже переконлива, можна зробити висновок, що таке рішення проблеми більш ніж ізящьно.

Таким чином, зле бідно я виклав свою точку зору і якісь думки з приводу використання "розумних покажчиків" на практиці і буду дуже радий якщо будуть які-небудь відгуки, як позитивні, так і критикують

Якщо є що мені сказати пишіть сюди.

Повні вихідні коди за все, про що тут говорилося (за винятком MP і H) можна знайти тут

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*