std::shared_ptr и weak_ptr
У объекта, которым владеет std::unique_ptr, владелец всегда один. Но бывает, что объектом пользуются несколько частей программы, и заранее неизвестно, какая закончит последней. Для этого есть std::shared_ptr: его можно копировать, и все копии владеют одним объектом.
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> a = std::make_shared<int>(7);
std::shared_ptr<int> b = a;
*b = 8;
std::cout << *a << ' ' << a.use_count() << '\n'; // 8 2
b = nullptr;
std::cout << a.use_count() << '\n'; // 1
}std::make_shared<int>(7) создаёт число в куче вместе с первым владельцем. Строка std::shared_ptr<int> b = a; копирует не число, а владение. a и b указывают на одно и то же число, поэтому после *b = 8 значение *a тоже равно 8.
Счётчик владельцев
Это общее владение (shared ownership): объект принадлежит всем копиям сразу. shared_ptr считает владельцев, и use_count() возвращает их число. Каждая копия увеличивает счётчик на единицу. Когда владелец уничтожается или ему присваивают nullptr, счётчик уменьшается.
Объект удаляется, когда счётчик доходит до нуля, то есть вместе с последним владельцем. Подсчёт стоит времени и памяти, поэтому shared_ptr берут, только когда владельцев действительно несколько. В остальных случаях хватает std::unique_ptr.
Слабый указатель
std::weak_ptr — слабый указатель (weak pointer): он следит за объектом, которым владеют shared_ptr, но сам им не владеет. Его создают из shared_ptr, и use_count() от этого не растёт. Метод lock() возвращает shared_ptr на объект или пустой shared_ptr, если объект уже удалён:
std::shared_ptr<int> owner = std::make_shared<int>(5);
std::weak_ptr<int> watcher = owner;
std::cout << owner.use_count() << '\n'; // 1
owner = nullptr;
std::cout << (watcher.lock() == nullptr) << '\n'; // 1Частая ошибка
Два объекта владеют друг другом через shared_ptr:
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "~Node\n"; }
};
int main() {
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b;
b->next = a; // ✗
}Стрелка работает и с умными указателями. Программа ничего не печатает. В конце main уничтожаются a и b, но каждый Node по-прежнему владеет соседом, и оба счётчика остаются равны единице. Деструкторы не вызываются, и оба объекта утекают.
Такой цикл разрывает слабый указатель. Если объявить поле как std::weak_ptr<Node> next;, оно не владеет соседом, оба счётчика доходят до нуля, и программа печатает ~Node дважды. В настоящих программах слабой обычно делают одну связь из двух — например, от дочернего объекта к родительскому.