Для чего нужен std::weak_ptr?

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

Ответ

std::weak_ptr<T> — это «слабый» умный указатель, который не владеет объектом и не увеличивает счетчик ссылок (use_count) std::shared_ptr. Его основное назначение — разрушение циклических ссылок между shared_ptr и безопасное наблюдение за объектом, время жизни которого управляется shared_ptr.

Основные сценарии использования:

  1. Разрушение циклических зависимостей: Классический пример — двунаправленная связь или родитель-ребенок в графах.
  2. Кэширование: Хранение «слабых» ссылок на объекты, которые могут быть удалены, если на них больше нет «сильных» ссылок (shared_ptr).
  3. Наблюдение (observer): Временное получение доступа к ресурсу, если он еще жив, без продления его времени жизни.

Как с ним работать: weak_ptr нельзя разыменовать напрямую. Сначала нужно получить из него shared_ptr с помощью метода lock().

Пример с циклической зависимостью:

#include <iostream>
#include <memory>

class Node {
public:
    std::string name;
    std::shared_ptr<Node> partner; // Сильная ссылка
    // std::weak_ptr<Node> partner; // Правильное решение: слабая ссылка

    Node(const std::string& n) : name(n) {
        std::cout << "Node " << name << " created.n";
    }
    ~Node() {
        std::cout << "Node " << name << " destroyed.n";
    }
};

int main() {
    std::cout << "=== Example with shared_ptr (Cyclic Reference) ===n";
    {
        auto alice = std::make_shared<Node>("Alice");
        auto bob = std::make_shared<Node>("Bob");
        alice->partner = bob; // bob.use_count() = 2
        bob->partner = alice; // alice.use_count() = 2 -> ЦИКЛ!
        // При выходе из области видимости деструкторы НЕ вызовутся из-за цикла.
    }
    std::cout << "Scope ended. Memory leak!nn";

    std::cout << "=== Example with weak_ptr (Correct) ===n";
    {
        auto alice = std::make_shared<Node>("Alice");
        auto bob = std::make_shared<Node>("Bob");
        // Используем weak_ptr для разрыва цикла владения
        std::weak_ptr<Node> aliceWeak = alice;
        std::weak_ptr<Node> bobWeak = bob;

        // Для доступа к партнеру используем lock()
        if (auto partnerPtr = bobWeak.lock()) { // Пытаемся получить shared_ptr
            std::cout << "Bob's partner (via weak_ptr) is: " << partnerPtr->name << std::endl;
        } else {
            std::cout << "Bob's partner no longer exists.n";
        }
        // При выходе из области видимости alice и bob будут корректно уничтожены,
        // т.к. weak_ptr не мешает счетчику ссылок достичь нуля.
    }
    std::cout << "Scope ended. No memory leak.n";
    return 0;
}

lock() возвращает shared_ptr на объект, если он еще существует (счетчик use_count > 0), или nullptr в противном случае. Это потокобезопасная операция.