C++如何手写一个智能指针?C++ RAII与引用计数实现【底层实践】

智能指针核心是用RAII自动管理堆内存,避免裸指针的内存泄漏与崩溃;引用计数需独立分配内存并原子操作;拷贝/赋值需同步更新计数;重载*、->等运算符以支持指针式访问。

智能指针核心要解决什么问题

裸指针容易忘 delete、重复 delete、提前释放,导致内存泄漏或崩溃。手写智能指针本质是用对象生命周期(构造/析构)自动管理堆内存——这就是 RAII(Resource Acquisition Is Initialization)。关键不在“指针像指针”,而在“析构时自动释放资源”。

引用计数怎么存才安全

引用计数不能和托管对象混在一起(否则 new T 无法带计数),也不能单独 new 一个 int(多一次分配,且难保证线程安全)。主流做法是额外分配一块内存,把计数和指针一起存:

  • new char[sizeof(size_t) + sizeof(T*)] 分配原始内存
  • 用 placement new 在前 sizeof(size_t) 字节放引用计数,在后放 T* 指针
  • 析构时先调用 T 的析构函数,再释放整块内存

这样只做一次堆分配,缓存友好,也方便原子操作(比如用 std::atomic_size_t 存计数)。

拷贝和赋值必须同步更新计数

拷贝构造函数和 operator= 不是简单复制指针,而是:增加原对象的引用计数,让新对象共享同一份资源;同时原对象析构或重置时,要递减计数,仅当计数归零才真正 delete。

  • 拷贝构造:++count,ptr = other.ptr
  • 赋值:先对当前对象做“减一并可能 delete”,再 ++other.count,更新 ptr
  • reset():减一并可能 delete,再 new 新对象、计数设为 1

注意 operator= 要自赋值安全(检查 this == &other)。

解引用和成员访问要像原生指针

重载 *-> 是必须的:

  • * 返回 *ptr(不是 ptr 的拷贝,是引用)
  • -> 返回 ptr(让 ptr->func() 可用)
  • 还建议重载 get() 返回原始指针,use_count() 查看计数,operator bool() 支持 if (sp) 判断是否非空

别忘了 const 版本:const T* get() const,T& operator*() const 等,否则 const 对象无法解引用。

基本上就这些。RAII 是骨架,引用计数是血肉,而 operator 重载是让它“用起来像指针”的最后一层封装。不复杂但容易忽略细节,比如计数初始化、析构顺序、线程安全边界——生产环境推荐 std::shared_ptr,但手写一遍能真正看清内存管理的因果链。