SeniorCodeOccasionalNot answered yet
Extend shared_ptr implementation to support weak_ptr.
Given a reference-counted SharedPtr<T> with a control block holding atomic strong and weak counts, implement WeakPtr<T> that observes the object without keeping it alive.
Constraints:
the last SharedPtr being destroyed; return an empty SharedPtr if the object is dead.
- A
WeakPtraffects only the weak count, never the strong count. lock()must atomically check strong > 0 and increment it (CAS loop) to avoid a race with- The control block must survive until both strong and weak counts reach zero.
#include <atomic>
template<typename T> class SharedPtr;
template<typename T>
class WeakPtr {
friend class SharedPtr<T>;
public:
WeakPtr() = default;
explicit WeakPtr(const SharedPtr<T>& sp) noexcept;
bool expired() const noexcept;
SharedPtr<T> lock() const noexcept;
// your code here
};
Write the implementation.
WeakPtr<T> shares the control block but increments only the weak count. lock() atomically checks strong > 0 and increments it via CAS; returns empty SharedPtr if the object is dead. The control block survives until both counts hit zero.
- ✗Not using a CAS loop in
lock()— between checking count > 0 and incrementing it, another thread could destroy the lastSharedPtr; must usecompare_exchange_weakin a loop - ✗Freeing the control block when strong count reaches zero —
WeakPtrstill needs it; only free when both counts are zero - ✗Circular
shared_ptrownership withoutweak_ptr— two objects holdingshared_ptrto each other never get destroyed; break cycles withweak_ptr
- →How does
std::enable_shared_from_thisuse aweak_ptrinternally to produce ashared_ptrtothis? - →What happens if you call
lock()on aweak_ptrfrom the destructor of the managed object?
Contents
Extending SharedPtr to Support WeakPtr
Control Block (same as before)
#include <atomic>
struct ControlBlock {
std::atomic<int> strong{1};
std::atomic<int> weak{1};
};
WeakPtr
template<typename T>
class WeakPtr {
friend class SharedPtr<T>;
public:
WeakPtr() = default;
explicit WeakPtr(const SharedPtr<T>& sp) noexcept
: ptr_(sp.ptr_), cb_(sp.cb_) {
if (cb_) cb_->weak.fetch_add(1, std::memory_order_relaxed);
}
WeakPtr(const WeakPtr& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
if (cb_) cb_->weak.fetch_add(1, std::memory_order_relaxed);
}
WeakPtr(WeakPtr&& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
~WeakPtr() { release(); }
bool expired() const noexcept {
return !cb_ || cb_->strong.load(std::memory_order_acquire) == 0;
}
// Atomically upgrade to SharedPtr or return empty if object is dead
SharedPtr<T> lock() const noexcept {
if (!cb_) return {};
int count = cb_->strong.load(std::memory_order_relaxed);
while (count > 0) {
if (cb_->strong.compare_exchange_weak(count, count + 1,
std::memory_order_acq_rel,
std::memory_order_relaxed)) {
SharedPtr<T> sp;
sp.ptr_ = ptr_;
sp.cb_ = cb_;
return sp;
}
}
return {};
}
private:
void release() {
if (!cb_) return;
if (cb_->weak.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete cb_;
cb_ = nullptr;
ptr_ = nullptr;
}
T* ptr_ = nullptr;
ControlBlock* cb_ = nullptr;
};
Test
#include <cassert>
struct Obj { static int alive; Obj() { ++alive; } ~Obj() { --alive; } };
int Obj::alive = 0;
int main() {
WeakPtr<Obj> wp;
{
SharedPtr<Obj> sp(new Obj{});
wp = WeakPtr<Obj>(sp);
assert(!wp.expired());
auto locked = wp.lock();
assert(locked);
assert(sp.use_count() == 2);
}
// sp destroyed → strong == 0 → object deleted
assert(wp.expired());
assert(Obj::alive == 0);
auto locked = wp.lock();
assert(!locked); // object already dead
}Contents