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_ptr сразу, это общее владение. В камере хранения так выглядит ячейка, на которую выдали несколько номерков: вещь отдают, только когда вернули последний из них.
shared_ptr считает владельцев, и use_count() возвращает их число. Каждая копия увеличивает счётчик на единицу. Когда владелец уничтожается или ему присваивают nullptr, счётчик уменьшается.
Пройдите программу по шагам и следите, как каждая копия меняет счётчик и в какой момент удаляется Song.
1–3#include <iostream>, <memory>4struct Song {5 ~Song() { std::cout << "~Song\n"; }6};7 8void play(std::shared_ptr<Song> s) {9 std::cout << s.use_count() << "\n";10}11 12int main() {13 auto a = std::make_shared<Song>();14 std::cout << a.use_count() << "\n";15 {16 std::shared_ptr<Song> b = a;17 std::cout << a.use_count() << "\n";18 play(b);19 }20 std::cout << a.use_count() << "\n";21 a = nullptr;22 std::cout << "end\n";23}
make_shared создаёт Song в куче и первого владельца a
Объект удаляется, когда счётчик доходит до нуля, то есть вместе с последним владельцем. Подсчёт стоит времени и памяти, поэтому shared_ptr берут, только когда владельцев несколько. В остальных случаях хватает std::unique_ptr.
Слабый указатель
std::weak_ptr — слабый указатель: он следит за объектом, которым владеют 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 дважды. В настоящих программах слабой обычно делают одну связь из двух — например, от дочернего объекта к родительскому.
Главное
- Копии
std::shared_ptrвладеют одним объектом вместе, аuse_count()возвращает число владельцев. - Объект удаляется вместе с последним владельцем, когда счётчик доходит до нуля.
std::weak_ptrследит за объектом, но не владеет им:lock()возвращает пустойshared_ptr, если объект удалён.- Два объекта, которые владеют друг другом через
shared_ptr, утекают; одну из связей делаютweak_ptr.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.