We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← References, Pointers, Ownership step 4 of 4
Two owners, and neither one ever leaves
std::shared_ptr is where people go when ownership is unclear, and unclear
ownership is the one thing it does not fix. It is a precise tool for a
narrow case:
Use
shared_ptrwhen the object’s lifetime genuinely cannot be known at compile time — when the last of several independent owners, whichever it turns out to be, must be the one that destroys it.
If you can name the owner, use unique_ptr and hand out T* or T& to
everyone else. That is most code.
What it costs
unique_ptr |
shared_ptr |
|
|---|---|---|
| Size | one pointer | two pointers |
| Construction | none | a control-block allocation |
| Copy | deleted | an atomic increment |
| Destruction | a destructor call | an atomic decrement, then maybe destruction |
The atomics are the part that surprises people. They are paid whether or not
your program has threads, they do not optimise away, and they serialise cores
that touch the same count. A function taking shared_ptr<T> by value
pays two of them per call for no ownership it actually keeps — which is why
the rule is:
void inspect(const Widget& w); // best: I only look
void inspect(const std::shared_ptr<Widget>& p); // acceptable: may be null
void inspect(std::shared_ptr<Widget> p); // an atomic pair, wasted
Pass the object unless the callee might extend its life.
The failure mode a reference count cannot see
Reference counting cannot collect cycles. If A holds a shared_ptr to B and
B holds one back to A, each count stays at one after every external owner
has gone. Neither is destroyed, the memory is unreachable, and no destructor
ever runs — so anything either of them owned leaks too.
Trees are where this happens, because a parent link is such an obvious thing to add:
struct Node {
std::vector<std::shared_ptr<Node>> children; // parent owns children
std::shared_ptr<Node> parent; // ...and children own parent
};
Nothing is wrong with either line on its own.
weak_ptr is how you say “I am watching this”
A std::weak_ptr refers to an object without owning it and without keeping
it alive. It is not dereferenceable — you must ask for a real owner first:
std::shared_ptr<Node> p = weak.lock(); // null if the object is gone
if (p != nullptr) { ... }
That lock() is the whole interface, and its null case is not a formality:
between your check and your use, the last owner may have released it, and
holding the shared_ptr is what stops that happening.
So: the owning direction is shared_ptr, the back-reference is
weak_ptr. Here, children are owned by their parent, and the parent link
points back — so the parent link is the weak one.
One more note on make_shared
std::make_shared<T>(...) puts the control block and the object in one
allocation instead of two. Prefer it — with one caveat worth knowing: that
single block cannot be freed until the last weak_ptr is gone too, so a
large object watched by long-lived weak_ptrs keeps its bytes around after
its destructor has run.
Your task
depths is given: it builds a tree from a parent list and returns each
node’s depth. Do not change it.
Change Node so the tree does not leak. Node counts how many instances are
alive and the harness reads that count after depths has returned and every
local has been destroyed — so it must be zero.
You may change the type of the parent member and the body of parent().
Nothing else needs to move.
Stuck?
C++ reference solution
Sign in to attempt this problem and reveal the reference solution.