Урок №195. std::weak_ptr. Циклическая зависимость с std::shared_ptr

  Юрий  | 

    | 

  Обновл. 5 Янв 2019  | 

 882

 ǀ   5 

В предыдущем уроке мы рассматривали std::shared_ptr и как с его помощью сразу несколько умных указателей могут владеть одним, динамически выделенным, ресурсом. Однако иногда это может быть проблематично.

Пересечение указателей

Например, рассмотрим случай, когда два умных указателя типа std::shared_ptr владеют двумя разными объектами и «пересекаются» между собой:

Здесь мы динамически выделяем два объекта («Anton» и «Ivan») класса Human и, используя 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 был разработан для решения проблемы c «циклической зависимостью», описанной выше. 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 (5 оценок, среднее: 5,00 из 5)
Загрузка...

Комментариев: 5

  1. Аватар Артем:

    Все отлично для новичков, но хотелось бы побольше нюансов и задачек. А так спасибо

  2. Аватар korvell:

    Спасибо за то, что сделал оглавление. Очень полезно 🙂

    1. Юрий Юрий:

      Пожалуйста 🙂

      1. Аватар Артёмка:

        Юра, а сколько всего уроков? 196 последний? Или будут еще?

        1. Юрий Юрий:

          Ещё перевести осталось около 20-ти уроков.

Добавить комментарий

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