умные указателиsmart pointers
Невладеющее наблюдение за объектом, управляемым shared_ptr — не влияет на счётчик владения и не продлевает жизнь объекта. Основной инструмент разрыва циклических ссылок между shared_ptr.A non-owning observer of an object managed by a shared_ptr — it doesn't affect the ownership count and doesn't extend the object's lifetime. The primary tool for breaking cyclic references between shared_ptr instances.
std::weak_ptr<T> — умный указатель, ссылающийся на объект, которым владеет shared_ptr, но не увеличивающий strong count control block-а. Чтобы получить доступ к объекту, weak_ptr нужно временно «поднять» до shared_ptr через lock() — эта операция атомарно проверяет, жив ли ещё объект, и либо возвращает валидный shared_ptr, либо пустой.std::weak_ptr<T> is a smart pointer referring to an object owned by a shared_ptr, but without incrementing the control block's strong count. To access the object, a weak_ptr must be temporarily "promoted" to a shared_ptr via lock() — this operation atomically checks whether the object is still alive and either returns a valid shared_ptr or an empty one.
struct Node {
std::shared_ptr<Node> parent; // цикл, если child держит parent// a cycle if child holds parent
std::shared_ptr<Node> child;
};
struct Node {
std::weak_ptr<Node> parent; // не владеет — цикл разорван// doesn't own — the cycle is broken
std::shared_ptr<Node> child;
};
weak_ptr решает две задачи, для которых shared_ptr структурно не подходит:weak_ptr solves two problems that shared_ptr structurally isn't suited for:
Родитель-ребёнок, наблюдатель-субъект, кэш с обратными ссылками — везде, где два объекта могут держать shared_ptr друг на друга, нужен способ выразить «эта ссылка не должна учитываться в подсчёте владения», иначе счётчик никогда не дойдёт до нуля.Parent-child, observer-subject, a cache with back-references — anywhere two objects can hold a shared_ptr to each other, there needs to be a way to express "this reference shouldn't count toward ownership," otherwise the count never reaches zero.
Кэш или реестр объектов, которым не нужно владеть объектами (они управляются кем-то другим), но нужно безопасно проверять, живы ли они ещё, до weak_ptr решался через голые указатели с риском обращения к уже удалённому объекту (dangling pointer) — weak_ptr делает эту проверку атомарной и безопасной через lock().A cache or registry of objects that doesn't need to own them (they're managed by someone else) but does need to safely check whether they're still alive was, before weak_ptr, handled with raw pointers, carrying the risk of touching an already-deleted object (a dangling pointer) — weak_ptr makes that check atomic and safe via lock().
Механизм, позволяющий объекту безопасно создать shared_ptr на самого себя изнутри метода, хранит внутри себя именно weak_ptr — без него было бы невозможно избежать двойного независимого control block-а.The mechanism that lets an object safely create a shared_ptr to itself from inside a method stores, internally, exactly a weak_ptr — without it, avoiding a second, independent control block would be impossible.
weak_ptr хранит указатель на тот же control block, что и связанные с ним shared_ptr, но инкрементирует не strong count, а отдельный weak count. Control block целиком (память под счётчики) не освобождается, пока жив хотя бы один weak_ptr — даже если сам объект T уже уничтожен, потому что strong count дошёл до нуля раньше.weak_ptr stores a pointer to the same control block as its associated shared_ptr instances, but increments a separate weak count instead of the strong count. The entire control block (the memory for the counters) isn't freed while at least one weak_ptr is alive — even if the object T itself has already been destroyed because the strong count reached zero earlier.
std::shared_ptr<Widget> sp = std::make_shared<Widget>();
std::weak_ptr<Widget> wp = sp; // weak count++, strong count не меняется// weak count++, strong count is unchanged
if (auto locked = wp.lock()) { // атомарно: strong count > 0?// atomically: strong count > 0?
locked->use(); // locked — полноценный shared_ptr,// locked is a full-fledged shared_ptr,
// strong count временно ++ на время использования// strong count is temporarily ++ for the duration of use
} else {
// объект уже удалён — lock() вернул пустой shared_ptr// the object is already deleted — lock() returned an empty shared_ptr
}
sp.reset(); // strong count → 0, Widget уничтожен// strong count → 0, Widget is destroyed
bool dead = wp.expired(); // true — но expired() сам по себе гонка,// true — but expired() is itself a race,
// предпочтительнее сразу lock()// calling lock() directly is preferable
lock() — атомарная операция именно потому, что между проверкой «жив ли объект» и получением shared_ptr на него в многопоточном коде другой поток мог успеть уничтожить последний shared_ptr — раздельные expired() + отдельное построение shared_ptr были бы гонкой.lock() is atomic precisely because, in multithreaded code, another thread could destroy the last shared_ptr between the "is the object alive" check and obtaining a shared_ptr to it — a separate expired() call followed by constructing a shared_ptr would be a race.
if (!wp.expired()) { auto sp = wp.lock(); ... } — между вызовом expired() и lock() другой поток может уничтожить объект. Единственный безопасный паттерн — сразу вызывать lock() и проверять результат, без промежуточного expired().if (!wp.expired()) { auto sp = wp.lock(); ... } — between the expired() call and lock(), another thread can destroy the object. The only safe pattern is calling lock() directly and checking its result, without an intermediate expired().
Память объекта T освобождается сразу при strong count == 0 (вызывается деструктор), но память control block-а (счётчики, deleter) остаётся выделенной, пока жив хотя бы один weak_ptr — при make_shared это одна общая аллокация с объектом, поэтому вся она держится дольше, чем можно было ожидать.Object T's memory is released as soon as strong count == 0 (the destructor is called), but the control block's memory (counters, deleter) stays allocated as long as at least one weak_ptr is alive — with make_shared this is one combined allocation with the object, so the whole thing sticks around longer than one might expect.
У weak_ptr нет operator* и operator-> — попытка обратиться к объекту без явного lock() не компилируется. Это осознанное ограничение API, которое заставляет каждый раз явно обрабатывать случай «объект уже удалён».weak_ptr has no operator* or operator-> — trying to access the object without an explicit lock() simply doesn't compile. This is a deliberate API restriction that forces you to explicitly handle the "object already deleted" case every time.