SeniorCodeOccasionalNot answered yet
Implement a basic std::shared_ptr with reference counting.
Implement a basic SharedPtr<T> with reference counting: shared ownership of a heap object that is deleted when the last owner goes away.
Constraints:
- Store the object pointer plus a heap-allocated control block with an atomic reference count.
- Copy increments the count; the destructor decrements and deletes the object at zero.
- Support move (steal, leave source empty) and copy assignment that is self-assignment-safe.
- The reference count must be
std::atomicso concurrent copies are race-free.
#include <atomic>
template<typename T>
class SharedPtr {
public:
explicit SharedPtr(T* ptr = nullptr);
SharedPtr(const SharedPtr& other) noexcept;
SharedPtr(SharedPtr&& other) noexcept;
SharedPtr& operator=(const SharedPtr& other) noexcept;
~SharedPtr();
int use_count() const noexcept;
// your code here
};
Write the implementation.
SharedPtr<T> holds the object pointer plus a pointer to a heap-allocated control block with an std::atomic<int> reference count. Copy increments it; destructor decrements and deletes the object when it reaches zero.
- ✗Using a non-atomic reference count — concurrent copies from different threads cause races on the counter, leading to double-free or leak
- ✗Storing the ref count inside the managed object — prevents
shared_ptrfrom a raw pointer after the fact and complicatesweak_ptrsupport - ✗Not handling
operator=self-assignment correctly — increment new count before decrement old to avoid freeing the object when assigning to itself
- →How does
std::enable_shared_from_thiswork internally? - →Why does
std::make_shareduse one allocation butshared_ptr<T>(new T)uses two?
Contents
Basic shared_ptr with Reference Counting
Control Block
#include <atomic>
#include <utility>
struct ControlBlock {
std::atomic<int> strong{1};
std::atomic<int> weak{1}; // +1 from strong; cleared when strong == 0
};
SharedPtr Implementation
template<typename T>
class SharedPtr {
public:
explicit SharedPtr(T* ptr = nullptr) {
if (ptr) {
ptr_ = ptr;
cb_ = new ControlBlock{};
}
}
SharedPtr(const SharedPtr& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
if (cb_) cb_->strong.fetch_add(1, std::memory_order_relaxed);
}
SharedPtr(SharedPtr&& other) noexcept
: ptr_(other.ptr_), cb_(other.cb_) {
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
if (other.cb_) other.cb_->strong.fetch_add(1, std::memory_order_relaxed);
release();
ptr_ = other.ptr_;
cb_ = other.cb_;
}
return *this;
}
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
cb_ = other.cb_;
other.ptr_ = nullptr;
other.cb_ = nullptr;
}
return *this;
}
~SharedPtr() { release(); }
T* get() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
int use_count() const noexcept {
return cb_ ? cb_->strong.load(std::memory_order_relaxed) : 0;
}
private:
void release() {
if (!cb_) return;
if (cb_->strong.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr_;
ptr_ = nullptr;
if (cb_->weak.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete cb_;
cb_ = nullptr;
}
}
T* ptr_ = nullptr;
ControlBlock* cb_ = nullptr;
};
template<typename T, typename... Args>
SharedPtr<T> makeShared(Args&&... args) {
return SharedPtr<T>(new T(std::forward<Args>(args)...));
}
Test
#include <cassert>
struct Obj {
int val;
static int alive;
Obj(int v) : val(v) { ++alive; }
~Obj() { --alive; }
};
int Obj::alive = 0;
int main() {
{
SharedPtr<Obj> a(new Obj(42));
assert(a->val == 42);
assert(a.use_count() == 1);
assert(Obj::alive == 1);
SharedPtr<Obj> b = a;
assert(a.use_count() == 2);
SharedPtr<Obj> c = std::move(b);
assert(!b);
assert(c.use_count() == 2);
}
assert(Obj::alive == 0);
}Contents