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

 2070

На попередньому уроці ми розглянули розумний вказівник 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 Зірок (40 оцінок, середня: 5,00 з 5)
Завантаження...

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

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