1. 设计目标与范围
设计目标
在 C++ 中,智能指针的核心能力是通过引用计数实现资源的自动管理。本文从零开始实现一个简化版的 SharedPtr,聚焦于引用计数机制、正确的拷贝与释放语义,以及对原始资源的透明访问。通过这套实现,你可以清晰看到资源生命周期是如何被多份指针共同管理的。
该实现的目标是让你理解:控制块如何承载指针和计数、拷贝时如何让计数增加、析构时如何让资源在最后一个引用被释放时才回收,以及如何提供与原生指针等价的接口以减少学习成本。
实现边界与约定
我们仅实现最常见的场景:独享所有权资源的共享,不涉及复杂的 weak_ptr、循环引用检测等高级特性。线程安全也被作为可选扩展点提出,在单线程场景下给出一个简单实现即可。

通过这套边界,我们可以把复杂度聚焦在引用计数更新逻辑、资源释放时机、以及对外暴露的访问接口上,从而帮助读者快速理解从零实现一个智能指针的关键步骤。
2. 数据结构设计
核心结构
核心设计思路是把资源指针和引用计数放在一个独立的控制块中,多个 SharedPtr 实例共享同一个控制块,从而实现对资源的引用计数管理。控制块通常包含两个要素:原始指针和一个引用计数值。
将引用计数与资源对象解耦,可以让拷贝、移动和释放操作只触及控制块,从而避免对源对象本身造成污染,也方便未来扩展为弱引用等高级特性。
接口设计
实现的 Public 接口要与标准库的 std::shared_ptr 相近,包括:构造、析构、拷贝构造、拷贝赋值、解引用、获取原始指针、以及 use_count 等辅助方法。
为了可读性,控制块通常作为 SharedPtr 的私有实现细节存在,外部只通过指针语义与操作符实现与原生指针等价的体验。
3. 代码实现:控制块与引用计数
控制块设计
控制块承担两项职责:维护原始指针和 维护引用计数。简单版本中,控制块不涉及弱引用,且计数在单线程环境下为普通整型。下面给出一个最小可工作版本的控制块结构。
template<typename T>
struct ControlBlock {T* ptr;size_t ref_count;ControlBlock(T* p): ptr(p), ref_count(1) {}
};
通过该控制块,SharedPtr 的拷贝构造会将引用计数+1,析构时会将计数-1并在计数为 0 时释放资源和控制块本身。这样的分离式设计,是实现可共享引用计数智能指针的核心思路。
4. 代码实现:SharedPtr 类
构造与析构
SharedPtr 应提供默认构造、通过原始指针构造、拷贝构造与析构等基本操作。析构时需要确保对控制块的引用计数进行合理更新,若计数降为 0,则释放资源。
template<typename T>
class SharedPtr {
public:// 默认构造SharedPtr(): ctrl(nullptr) {}// 通过原始指针构造explicit SharedPtr(T* p): ctrl(new ControlBlock<T>(p)) {}// 拷贝构造SharedPtr(const SharedPtr<T>& other) {ctrl = other.ctrl;if (ctrl) ++(ctrl->ref_count);}// 析构~SharedPtr() {release();}private:struct ControlBlockBase {virtual ~ControlBlockBase() = default;virtual void release() = 0;};template<typename U>struct ControlBlock : ControlBlockBase {U* ptr;size_t ref_count;ControlBlock(U* p): ptr(p), ref_count(1) {}void release() override { if (--ref_count == 0) { delete ptr; delete this; } }};ControlBlockBase* ctrl;void release() {if (ctrl) {ctrl->release();ctrl = nullptr;}}
};
以上实现演示了一个较为简化的控制块绑定模式:控制块通常是多态类型,以便将不同模板参数的指针都包裹在同一管理结构中。实际使用中,可以将控制块内的信息设计得更紧凑以减少虚函数开销。
5. 拷贝、移动、重置的语义与实现
拷贝语义
在拷贝构造和拷贝赋值中,需要确保引用计数的正确自增以及避免资源重复释放。若新对象成功构造,则原对象应保持不变,直到最终引用被释放。
// 拷贝构造
SharedPtr(const SharedPtr<T>& other) {ctrl = other.ctrl;if (ctrl) ++(static_castref_count);
}// 拷贝赋值(简化版)
SharedPtr& operator=(const SharedPtr<T>& other) {if (this != &other) {release();ctrl = other.ctrl;if (ctrl) ++(static_castref_count);}return *this;
}
通过把释放逻辑集中在一个私有方法中,可以确保在异常路径下也能正确处理资源回收与计数更新。
6. 访问接口与使用示例
解引用与访问
对外暴露的接口需要实现常见的解引用运算符,以及获取原始指针的能力,以便在语法层面尽量接近原生指针的使用体验。解引用运算符、箭头运算符、以及 get()、use_count() 等方法是最直观的入口。
template<typename T>
T& operator*() const { return * ctrl->ptr; }
T* operator->() const { return ctrl->ptr; }
T* get() const { return ctrl ? ctrl->ptr : nullptr; }
size_t use_count() const { return ctrl ? ctrl->ref_count : 0; }
使用示例演示了如何在不引入额外所有权语义的前提下,通过简单的操作符实现对目标对象的访问与生命周期同步。
7. 线程安全与扩展
原子性与并发
在单线程场景下,非原子计数的实现已经足够直观且高效。若希望在多线程环境中安全地进行共享,请引入原子性保障。引用计数应变为原子类型,并在拷贝、析构以及重置路径中使用原子操作(如 fetch_add/fetch_sub)来避免竞态条件。
一个可扩展的方向是将控制块分离为独立的模板结构,提供 WeakPtr、enable_shared_from_this 等特性,逐步接近标准库实现的能力边界。
#include <atomic>
template<typename T>
struct AtomicControlBlock {T* ptr;std::atomic<size_t> ref_count;AtomicControlBlock(T* p): ptr(p), ref_count(1) {}
}; 

