Урок №203. Розумний вказівник std::weak_ptr

  Юрій  | 

  Оновл. 1 Кві 2021  | 

 104

На попередньому уроці ми розглянули розумний вказівник std::shared_ptr і те, як з його допомогою відразу кілька розумних вказівників можуть володіти одним динамічно виділеним ресурсом. Однак, іноді це може бути проблематично.

Перетин розумних вказівників

Наприклад, розглянемо випадок, коли два розумних вказівники типу std::shared_ptr володіють двома різними об’єктами і «перетинаються» між собою:

Тут ми динамічно виділяємо два об’єкти (Anton і Ivan) класу Human і, використовуючи std::make_shared, передаємо їх в два окремо створених розумних вказівники типу std::shared_ptr. Потім «пов’язуємо» їх за допомогою дружньої функції partnerUp().

Результат виконання програми:

Anton created
Ivan created
Anton is now partnered with Ivan

І це все? Ніяких знищень? Чому? Зараз розберемося.

Після виклику функції partnerUp() у нас є 4 розумних вказівники типу std::shared_ptr:

   Два розумних вказівники вказують на об’єкт Ivan: ivan (з функції main()) і m_partner (з класу Human) об’єкта Anton.

   Два розумних вказівники вказують на об’єкт Anton: anton і m_partner об’єкта Ivan.

В кінці функції partnerUp() розумний вказівник ivan виходить з області видимості першим. Коли це відбувається, він перевіряє, чи є інші розумні вказівники, які володіють об’єктом Ivan класу Human. Є — m_partner об’єкта Anton. Через це розумний вказівник не знищує Ivan-а (якщо він це зробить, то m_partner об’єкта Anton залишиться “висячим” вказівником). Таким чином у нас залишається один розумний вказівник, який володіє Ivan-ом (m_partner об’єкта Anton) і два розумних вказівники, які володіють Anton-ом (anton і m_partner об’єкта Ivan).

Потім розумний вказівник anton виходить з області видимості, і відбувається те ж саме. anton перевіряє, чи є інші розумні вказівники, які також володіють об’єктом Anton класу Human. Є — m_partner об’єкта Ivan, тому об’єкт Anton не знищується. Таким чином, залишаються два розумних вказівники:

   m_partner об’єкта Ivan, який вказує на Anton-а;

   m_partner об’єкта Anton, який вказує на Ivan-а.

Потім програма завершує своє виконання, і ні об’єкт Anton, ні об’єкт Ivan не знищуються! По суті, Anton не дає знищити Ivan-а, а Ivan не дає знищити Anton-а.

Це той випадок, коли розумні вказівники типу std::shared_ptr формують циклічну залежність.

Циклічна залежність

Циклічна залежність (або «циклічні посилання») — це серія «посилань», де поточний об’єкт посилається на наступний, а останній об’єкт посилається на перший. Ці «посилання» не обов’язково повинні бути звичайними посиланнями в мові C++, вони можуть бути вказівниками, унікальними ID або будь-якими іншими засобами ідентифікації конкретних об’єктів.

В контексті std::shared_ptr цими «посиланнями» є вказівники.

Це саме те, що ми бачимо вище: Anton вказує на Ivan-а, а Ivan вказує на Anton-а. Аналогічно, A вказує на B, B вказує на C, а C вказує на A. Практична цінність такої циклічної залежності в тому, що поточний об’єкт «залишає в живих» (не дає знищити) наступний об’єкт.

Спрощена циклічна залежність

Виявляється, проблема циклічного посилання може виникнути навіть з одним розумним вказівником типу std::shared_ptr. Така циклічна залежність називається спрощеною. Хоча це рідко трапляється на практиці, але все ж розглянемо і цей випадок:

У прикладі, наведеному вище, коли ptr1 виходить з області видимості, він не знищує Item, оскільки член m_ptr класу Item також володіє Item-ом. Таким чином, не залишається нікого, хто міг би видалити Item (m_ptr ніколи не виходить з області видимості, тому він цього не зробить).

Результат виконання програми:

Item acquired

Все!

Що ж таке розумний вказівник std::weak_ptr?

Розумний вказівник std::weak_ptr був розроблений для вирішення вищеописаної проблеми «циклічної залежності». std::weak_ptr є спостерігачем — він може спостерігати і отримувати доступ до того ж об’єкту, на який вказує std::shared_ptr (або інший std::weak_ptr), але не рахуватися власником цього об’єкта. Пам’ятайте, коли std::shared_ptr виходить з області видимості, він перевіряє, чи є інші власники std::shared_ptr. std::weak_ptr власником не рахується!

Давайте перепишемо першу програму цього уроку, але вже з використанням std::weak_ptr:

Результат виконання програми:

Anton created
Ivan created
Anton is now partnered with Ivan
Ivan destroyed
Anton destroyed

Функціонально все працює майже ідентично програмі, наведеній на початку цього уроку. Однак тепер, коли ivan виходить з області видимості, він бачить, що немає іншого std::shared_ptr, який вказує на Ivan-а (std::weak_ptr з Anton-а не рахується). Тому він знищує Ivan-а. Те ж саме відбувається і з Anton-ом.

Використання розумного вказівника std::weak_ptr

Недоліком розумного вказівника std::weak_ptr є те, що його не можна використовувати напряму (немає оператора ->). Щоб використовувати std::weak_ptr, ви спочатку повинні конвертувати його в std::shared_ptr (за допомогою методу lock()), а потім вже використовувати std::shared_ptr. Наприклад, перепишемо вищенаведену програму:

Результат виконання програми:

Anton created
Ivan created
Anton is now partnered with Ivan
Ivan's partner is: Anton
Ivan destroyed
Anton destroyed

Нам не потрібно турбуватися про циклічну залежність зі змінною partner (типу std::shared_ptr), оскільки вона є простою локальною змінною всередині функції main() і знищується при завершенні виконання функції main().

Висновки

Розумний вказівник std::shared_ptr використовується для володіння одним динамічно виділеним ресурсом відразу декількома розумними вказівниками. Ресурс буде знищений, коли останній std::shared_ptr вийде з області видимості. std::weak_ptr використовується, коли потрібен розумний вказівник, який має доступ до ресурсу, але не вважається його власником.

Тест

Виправте вищенаведену програму зі спрощеною циклічною залежністю, щоб Item був коректно звільнений.

Відповідь

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (Немає Оцінок)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *