A cache holds pointers to sessions, but must not keep dead sessions alive — that is exactly what weak references are for. The object dies when the last strong reference goes; the control block dies later, when the last weak reference goes. Mix the two lifetimes up and you get either a leak or a double free.
A SharedPtr with a control block is already written. Implement WeakPtr: it observes the object without owning it. expired() says whether the object is gone; lock() returns a real SharedPtr (empty if the object died). The control block must live while at least one WeakPtr remains — and be freed with the last reference of either kind.
WeakPtr(const SharedPtr&), copy constructor and destructor workexpired() is true exactly when no strong references remainlock() returns an owning SharedPtr, or an empty one if expiredTwo counters, two lifetimes: strong == 0 kills the object; strong == 0 AND weak == 0 kills the control block. Update SharedPtr::release accordingly.
lock() is the only place that turns weak into strong: check expired(), bump the strong counter, hand the block to the private SharedPtr constructor.
Hit Submit (or ⌘/Ctrl + ↵) — test results will show up here.