Как предотвратить циклическую зависимость с помощью std::weak_ptr?

«Как предотвратить циклическую зависимость с помощью std::weak_ptr?» — вопрос из категории Управление памятью, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Циклическая зависимость возникает, когда два или более объекта, управляемых std::shared_ptr, хранят взаимные ссылки, что препятствует освобождению памяти, так как счётчик ссылок никогда не достигает нуля. std::weak_ptr решает эту проблему, так как он не увеличивает счётчик ссылок объекта, на который указывает.

Пример:

#include <iostream>
#include <memory>

class NodeB;

class NodeA {
public:
    std::shared_ptr<NodeB> b_ptr;
    ~NodeA() { std::cout << "NodeA destroyedn"; }
};

class NodeB {
public:
    // Используем weak_ptr, чтобы разорвать цикл
    std::weak_ptr<NodeA> a_weak_ptr;
    ~NodeB() { std::cout << "NodeB destroyedn"; }
};

int main() {
    auto a = std::make_shared<NodeA>();
    auto b = std::make_shared<NodeB>();

    a->b_ptr = b;          // b увеличивает счётчик ссылок на NodeB
    b->a_weak_ptr = a;     // a передаётся как weak_ptr, счётчик NodeA не увеличивается

    // При выходе из области видимости объекты будут корректно уничтожены
    return 0;
}

Ключевые моменты:

  • weak_ptr создаётся из shared_ptr.
  • Для доступа к объекту необходимо вызвать метод lock(), который возвращает std::shared_ptr. Если исходный объект уже удалён, lock() вернёт пустой shared_ptr.
    if (auto shared_a = b->a_weak_ptr.lock()) {
    // Безопасная работа с shared_a
    } else {
    // Объект NodeA больше не существует
    }
  • Это стандартный паттерн для отношений "владелец-наблюдатель", где один объект владеет другим, а второй лишь наблюдает за первым, не продлевая его время жизни.