手写SimpleSharedPtr智能指针

发布时间:2026/6/27 22:41:11
手写SimpleSharedPtr智能指针 RAII资源获取即初始化**RAII **是 C 中的一种编程惯用法通过对象的生命周期管理资源确保资源在对象构造时获取析构时释放避免泄漏。SimpleSharedPtr基本概念SimpleSharedPtr 是一个简化版的shared_ptr实现旨在帮助理解其核心机制。其基本功能包括1.共享所有权多个SimpleSharedPtr可以指向同一个对象共享该对象的所有权2.自动管理生命周期当最后一个SimpleSharedPtr被销毁或指向其他对象时管理的对象被自动释放3.引用计数内部维护一个引用计数记录有多少个SimpleSharedPtr实例指向同一个对象引用计数控制块的设计为了实现引用计数机制SimpleSharedPtr 需要一个控制块Control Block它包含引用计数ref_count记录有多少个 SimpleSharedPtr 指向同一个对象指向对象的指针ptr指向实际管理的对象控制块通常与被管理对象一起被分配但为了简化实现本示例将它们独立管理structControlBlock{intref_count;ControlBlock():ref_count(1){}};函数实现细节在深入完整代码之前我们先逐一剖析每个函数的功能与设计思路这样再看完整代码时会更有把握。1. ControlBlock 构造函数ControlBlock():ref_count(1){}功能初始化控制块将引用计数置为 1。设计思路控制块与对象绑定一旦创建就意味着「有一个SimpleSharedPtr正在管理该对象」所以初始计数必须为 1而不是 0。如果初始化为 0即刻触发释放逻辑导致悬空指针。2. SimpleSharedPtr::release()voidrelease(){if(control){control-ref_count--;if(control-ref_count0){deleteptr;deletecontrol;}}ptrnullptr;controlnullptr;}功能将引用计数减 1若归零则同时释放被管理对象与控制块最后将两指针置空。设计思路这是整个智能指针的核心清理逻辑。先判断control是否为空避免对空指针操作再递减并判断是否归零——只有当没有任何SimpleSharedPtr再指向该对象时才真正delete。最后统一置空防止悬空指针。把清理逻辑抽成一个私有函数避免了拷贝赋值、reset()、析构函数中重复写同一段代码。3. 无参构造函数SimpleSharedPtr():ptr(nullptr),control(nullptr){}功能创建一个不管理任何对象的空智能指针。设计思路与std::shared_ptr一致允许默认构造一个空指针方便声明变量后再赋值。4. 带参构造函数explicitSimpleSharedPtr(T*p):ptr(p){if(p){controlnewControlBlock();}else{controlnullptr;}}功能接管一个原始指针创建控制块并将引用计数初始化为 1。设计思路用explicit禁止隐式类型转换防止SimpleSharedPtrT sp new T(...)这种容易误用的写法。为什么不能写SimpleSharedPtrT sp new T(...)new T(...)返回的是T*类型的裸指针。如果构造函数没有explicit编译器会尝试隐式转换// 假设没有 explicit这条语句会被编译器理解为SimpleSharedPtrTspSimpleSharedPtrT(newT(...));这本质上是先用T*构造一个临时SimpleSharedPtr对象再通过拷贝构造函数把sp初始化为该临时对象。问题就出在这里临时对象即刻销毁临时SimpleSharedPtr在语句结束后就被析构了它的析构函数会调用release()把引用计数从 1 减到 0触发delete ptr释放刚创建的那个new T(...)对象。于是sp内部指针变成悬空指针后续使用会崩溃。真实场景中语句根本编不过因为SimpleSharedPtr的拷贝构造函数也需要访问控制块当临时对象析构后控制块已被释放二次释放风险极高。标准库std::shared_ptr同样用explicit避免了这种问题。显式写更清晰SimpleSharedPtrT sp(new T(...))直接调用带参构造函数一步到位没有临时对象的折腾。一句话总结explicit强制你「直接构造」而不能先用new隐式转换出一个临时智能指针再拷贝——后者会让所有权在临时对象析构时被错误释放。5. 析构函数~SimpleSharedPtr(){release();}功能在SimpleSharedPtr对象销毁时调用release()安全释放资源。设计思路直接复用release()无需重复写清理逻辑。RAII 的精髓就在于此——对象生命周期结束自动触发清理。6. 拷贝构造函数SimpleSharedPtr(constSimpleSharedPtrother):ptr(other.ptr),control(other.control){if(control){control-ref_count;}}功能从另一个SimpleSharedPtr拷贝构造共享同一个对象引用计数 1。设计思路拷贝不复制对象本身而是共享所有权。因此只需拷贝两个指针然后在control非空时递增引用计数。如果control为空源是空智能指针则不操作。7. 拷贝赋值运算符SimpleSharedPtroperator(constSimpleSharedPtrother){if(this!other){release();ptrother.ptr;controlother.control;if(control){control-ref_count;}}return*this;}功能先释放当前持有的资源再共享新对象的资源。设计思路先release()当前资源防止在接管新资源后旧资源永远无法释放。if (this ! other)处理自赋值a a避免释放后把other也清空。递增新control的引用计数表示多一个共享者。8. 移动构造函数SimpleSharedPtr(SimpleSharedPtrother)noexcept:ptr(other.ptr),control(other.control){other.ptrnullptr;other.controlnullptr;}功能从另一个SimpleSharedPtr「窃取」所有权引用计数不增加。设计思路移动语义的核心是资源转移而非共享。直接接管other的指针然后把other置空使其进入「已移动」状态。标记noexcept可让标准容器在扩容时使用移动而非拷贝提升性能。9. 移动赋值运算符SimpleSharedPtroperator(SimpleSharedPtrother)noexcept{if(this!other){release();ptrother.ptr;controlother.control;other.ptrnullptr;other.ptrnullptr;// 此处应改为 other.control nullptr详见源码处注释}return*this;}功能先释放当前资源再转移所有权。设计思路与移动构造几乎一样但多一步release()因为当前对象可能正管理旧资源。同样需要自赋值检查、置空other、标记noexcept。10. operator* 解引用运算符Toperator*()const{return*ptr;}功能返回被管理对象的引用。设计思路与原生指针行为一致提供*sp的用法。声明为const表示不修改SimpleSharedPtr本身不改变其所指。11. operator- 箭头运算符T*operator-()const{returnptr;}功能返回被管理对象的裸指针使调用方可通过-访问成员。设计思路与原生指针行为一致支持sp-method()的写法。12. use_count() 获取引用计数intuse_count()const{returncontrol?control-ref_count:0;}功能返回当前引用计数值空智能指针返回 0。设计思路提供调试与监控能力。三目运算符保证了空指针时安全返回 0不会访问空地址。13. get() 返回裸指针T*get()const{returnptr;}功能直接返回内部裸指针。设计思路为兼容需要裸指针的 C 风格 API同时遵循const语义——调用get()不改变智能指针本身。14. reset() 重置指针voidreset(T*pnullptr){release();ptrp;if(p){controlnewControlBlock();}else{pnullptr;}}功能释放当前资源可选择接管一个新对象。设计思路先release()释放旧资源。若传入新指针则为它创建新的控制块计数从 1 开始。若传入nullptr或不传参则变为空智能指针。这一设计让reset()可同时充当「释放」和「重新绑定」两个角色。SimpleSharedPtr实现完整代码SimpleSharedPtr.h#ifndefSIMPLESHAREDPTR_H#defineSIMPLESHAREDPTR_HstructControlBlock{intref_count;ControlBlock():ref_count(1){}};templatetypenameTclassSimpleSharedPtr{private:T*ptr;//指向管理的对象ControlBlock*control;//指向控制块voidrelease(){if(control){control-ref_count--;if(control-ref_count0){deleteptr;deletecontrol;}}ptrnullptr;controlnullptr;}public://无参构造SimpleSharedPtr():ptr(nullptr),control(nullptr){}//带参构造explicitSimpleSharedPtr(T*p):ptr(p){if(p){controlnewControlBlock();}else{controlnullptr;}}//析构函数~SimpleSharedPtr(){release();}//拷贝构造SimpleSharedPtr(constSimpleSharedPtrother):ptr(other.ptr),control(other.control){if(control){control-ref_count;}}//拷贝赋值SimpleSharedPtroperator(constSimpleSharedPtrother){if(this!other){release();ptrother.ptr;controlother.control;if(control){control-ref_count;}}return*this;}//移动构造SimpleSharedPtr(SimpleSharedPtrother)noexcept:ptr(other.ptr),control(other.control){other.ptrnullptr;other.controlnullptr;}//移动赋值SimpleSharedPtroperator(SimpleSharedPtrother)noexcept{if(this!other){//释放当前资源release();ptrother.ptr;controlother.control;other.ptrnullptr;other.ptrnullptr;}return*this;}//解引用操作符Toperator*()const{return*ptr;}//-运算符T*operator-()const{returnptr;}//获取引用计数intuse_count()const{returncontrol?control-ref_count:0;}//返回裸指针T*get()const{returnptr;}//重置指针voidreset(T*pnullptr){//释放资源release();ptrp;if(p){controlnewControlBlock();}else{pnullptr;}}};#endifTest.h(用于测试)#ifndefTEST_H#defineTEST_H#includeiostreamclassTest{private:intvalue;public:Test(intval):value(val){std::coutTest Constructorstd::endl;}~Test(){std::coutTest Destructorstd::endl;}voidshow()const{std::coutValue:valuestd::endl;}};#endifTEST_H测试#includeiostream#includeTest.h#includeSimpleSharedPtr.hintmain(){//默认构造SimpleSharedPtrTestptr1;std::coutptr1 use_count:ptr1.use_count()std::endl;//带参构造SimpleSharedPtrTestptr2(newTest(20));std::coutptr2 use_count:ptr2.use_count()std::endl;ptr2-show();//拷贝构造SimpleSharedPtrTestptr3ptr2;std::coutptr2 use_count:ptr2.use_count()std::endl;std::coutptr3 use_count:ptr3.use_count()std::endl;ptr3-show();//拷贝赋值ptr1ptr3;std::coutptr1 use_count:ptr1.use_count()std::endl;std::coutptr2 use_count:ptr2.use_count()std::endl;std::coutptr3 use_count:ptr3.use_count()std::endl;//reset()ptr2.reset(newTest(100));std::coutptr1 use_count:ptr1.use_count()std::endl;std::coutptr2 use_count:ptr2.use_count()std::endl;std::coutptr3 use_count:ptr3.use_count()std::endl;ptr3.reset();std::coutptr1 use_count:ptr1.use_count()std::endl;std::coutptr2 use_count:ptr2.use_count()std::endl;std::coutptr3 use_count:ptr3.use_count()std::endl;return0;}