代码仓库shanchuann/CPP-Learninng
shared_ptr 是 C++11 标准推出的共享所有权型 智能指针,既是废弃 auto_ptr 的全面替代方案,也弥补了 unique_ptr 独占语义无法适配多对象共用资源的核心短板。它基于引用计数机制 实现多指针共享同一堆对象,允许多个 shared_ptr 同时指向并协同管理同一份资源,依托 RAII 机制自动完成生命周期管控,无需手动释放内存,从根源解决共享场景下的内存泄漏、重复释放、悬空指针三大痛点,广泛适配容器存储、跨模块数据共享、多线程资源共用、多态对象管理等高频工程场景。
相较于 unique_ptr 的零额外性能开销, shared_ptr 会占用极小量堆内存存储引用计数控制块,以此换取极致的共享安全性,是 C++11 智能指针体系中适配范围最广的共享型内存管理工具,也是 STL 容器存储智能指针的标准首选方案。
特性梳理 shared_ptr 的所有特性均围绕共享所有权设计,兼顾安全性、灵活性与工程实用性,覆盖底层逻辑与日常使用全维度,核心特性梳理如下:
共享所有权模式 :打破独占限制,同一份资源可被多个 shared_ptr 共同持有,所有权归属全体共享指针,而非单一指针,资源生命周期由全体持有者共同决定;
引用计数管控生命周期 :内置全局共享的引用计数,通过计数动态增减精准判断资源释放时机,计数归零时自动调用删除器销毁对象,全程自动化无需手动干预;
支持拷贝与赋值 :开放拷贝构造与左值赋值运算符,拷贝或赋值时仅递增引用计数,不复制资源本体,效率远高于资源拷贝;
兼容移动语义 :支持移动构造与移动赋值,转移所有权时不修改引用计数,原指针自动置空,兼顾所有权转移灵活性与资源安全性;
支持自定义删除器 :与 unique_ptr 逻辑一致,可绑定专属删除器,不仅能管理堆内存对象、动态数组,还能适配文件句柄、网络套接字等非堆内存系统资源;
STL 容器兼容 :满足标准容器元素可拷贝、可赋值的硬性要求,可直接存入 vector 、 list 、 map 、 unordered_map 等所有 STL 容器,彻底规避 auto_ptr 存入容器的崩溃风险;
裸指针使用体验 :重载*、->运算符,日常调用语法与普通裸指针完全一致,上手门槛低,无需额外学习特殊用法。
引用计数机制 引用计数是 shared_ptr 的底层核心机制,所有共享逻辑、生命周期管控均围绕该机制展开,也是其与 unique_ptr 的核心设计差异,原理与运行规则严谨清晰,是掌握 shared_ptr 的关键。
控制块结构 每个被 shared_ptr 管理的对象,都会配套一个独立堆内存分配的引用计数控制块 ,控制块内包含两大原子计数:shared_ptr 强引用计数(_Uses ) 、weak_ptr 弱引用计数(_Weaks ) 。该控制块由所有指向同一资源的 shared_ptr 全局共享,确保计数实时同步,且计数增减采用原子操作 ,保障多线程并发场景下的计数准确性,避免数据竞争问题。
动态变更规则
初始化计数 :通过 new 对象构造第一个 shared_ptr 时,强引用计数初始化为 1 ,代表当前有 1 个指针持有该资源;
拷贝/赋值递增 :拷贝构造或左值赋值生成新 shared_ptr 时,对应资源的强引用计数自动+1 ;
析构/重置递减 : shared_ptr 析构、调用 reset 重置或接管新资源时,原资源强引用计数自动-1 ;
计数归零释放 :强引用计数减至 0 时,代表无任何指针持有该资源,立即调用删除器释放对象内存,弱引用计数归零时销毁控制块;
移动语义不改动计数 :移动构造或赋值仅做所有权转移,不修改引用计数,原指针置空,不影响资源持有者数量。
引用计数实操示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <memory> using namespace std;class Int {private : int value; public : Int (int x = 0 ) : value (x) { cout << "Create Int: " << value << endl; } ~Int () { cout << "Destroy Int: " << value << endl; } void PrintInt () const { cout << "Value: " << value << endl; } }; int main () { shared_ptr<Int> pInta (new Int(10 )) ; cout << "pInta 引用计数:" << pInta.use_count () << endl; shared_ptr<Int> pIntb (pInta) ; cout << "pInta/pIntb 引用计数:" << pInta.use_count () << endl; shared_ptr<Int> pIntc; pIntc = pInta; cout << "当前引用计数:" << pInta.use_count () << endl; return 0 ; }
标准创建规范 shared_ptr 的创建分为安全首选方案与特殊场景备选方案,工程开发中需严格遵守规范,杜绝各类内存风险,具体创建方式与注意事项如下:
首选方案: std::make_shared (补充缓存局部性优势) make_shared 是标准库提供的专属工厂函数,可一次性分配对象内存与引用计数控制块 ,将对象数据与引用计数在堆上连续存储,这带来了两大关键优势:
减少内存分配次数与碎片 :单次分配同时完成对象与控制块的内存申请,相比 new + shared_ptr 构造的两次分配,显著降低了内存碎片,提升了内存管理效率,同时异常安全性拉满,完全规避裸指针暴露风险。
提升缓存局部性( Cache Locality ) :由于对象内存与引用计数控制块在物理地址上相邻, CPU 访问时更易被同时加载到高速缓存( Cache )中,可有效减少缓存缺失( Cache Misses )次数。根据程序局部性原理(时间局部性与空间局部性),连续内存布局能让数据访问更贴合 CPU 缓存机制,当需要频繁访问对象或修改引用计数时,可将缓存缺失操作减少约一半,在性能敏感场景下能带来可观的速度提升。
1 2 3 4 shared_ptr<Int> pInt = make_shared <Int>(20 ); shared_ptr<Int> pEmpty = make_shared <Int>();
备选方案:裸指针直接构造 该方式仅适用于无法使用 make_shared 的特殊场景,比如对象构造函数私有、需要绑定自定义删除器等,使用时需牢记核心禁忌:严禁用同一裸指针初始化多个 shared_ptr ,否则会生成多组独立控制块,最终引发重复释放崩溃。
1 2 3 4 5 6 7 shared_ptr<Int> pInt (new Int(10 )) ;Int* ip = new Int (10 ); shared_ptr<Int> p1 (ip) ;shared_ptr<Int> p2 (ip) ;
补充说明 : 引入 Cache 的理论基础是程序局部性原理,包括时间局部性和空间局部性:
时间局部性 :最近被 CPU 访问的数据,短期内 CPU 还会再次访问;
空间局部性 :被 CPU 访问的数据附近的数据,短期内 CPU 也会访问。
因此,将刚访问过的数据缓存在 Cache 中,下次访问时可直接从 Cache 读取,速度能得到数量级提升。 CPU 访问的数据在 Cache 中存在,称为“命中”( Hit ),反之则称为“缺失”( Miss )。make_shared 的连续内存布局正是通过优化空间局部性,有效降低了 Cache Miss 概率。
shared_ptr 底层源码 以下为还原标准库核心逻辑的 shared_ptr 仿写源码,包含原子引用计数、自定义删除器、数组特化、拷贝/移动/析构全功能实现,适配底层原理学习,注释详细易懂,贴合标准库设计思路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include <memory> #include <atomic> #include <iostream> using namespace std;template <typename _Ty>struct My_Shared_Deleter { void operator () (_Ty* ptr) const { delete ptr; } }; template <typename _Ty>struct My_Shared_Deleter <_Ty[]> { void operator () (_Ty* ptr) const { delete [] ptr; } }; template <typename _Ty>class My_RefCount {private : _Ty* _Ptr; atomic<int > _Uses; atomic<int > _Weaks; public : My_RefCount (_Ty* ptr = nullptr ) : _Ptr(ptr), _Uses(0 ), _Weaks(0 ) { if (_Ptr) _Uses = 1 ; } void Incref () { ++_Uses; } void Incwref () { ++_Weaks; } int Decref () { return --_Uses; } int Decwref () { return --_Weaks; } int use_count () const { return _Uses.load (); } _Ty* get () const { return _Ptr; } }; template <class _Ty , class Deleter = My_Shared_Deleter<_Ty>>class My_Shared_Ptr {public : using pointer = _Ty*; using element_type = _Ty; using deleter_type = Deleter; private : pointer _Ptr; My_RefCount<_Ty>* _RefBlock; deleter_type _Deleter; public : My_Shared_Ptr () : _Ptr(nullptr ), _RefBlock(nullptr ) {} explicit My_Shared_Ptr (pointer p) : _Ptr(p), _RefBlock(nullptr) { if (_Ptr) _RefBlock = new My_RefCount <_Ty>(_Ptr); } My_Shared_Ptr (const My_Shared_Ptr& other) { _Ptr = other._Ptr; _RefBlock = other._RefBlock; if (_RefBlock) _RefBlock->Incref (); } My_Shared_Ptr (My_Shared_Ptr&& other) noexcept { _Ptr = other._Ptr; _RefBlock = other._RefBlock; other._Ptr = nullptr ; other._RefBlock = nullptr ; } ~My_Shared_Ptr () { if (_RefBlock && _RefBlock->Decref () == 0 ) { _Deleter(_Ptr); delete _RefBlock; } } My_Shared_Ptr& operator =(const My_Shared_Ptr& other) { if (this != &other) { if (_RefBlock && _RefBlock->Decref () == 0 ) { _Deleter(_Ptr); delete _RefBlock; } _Ptr = other._Ptr; _RefBlock = other._RefBlock; if (_RefBlock) _RefBlock->Incref (); } return *this ; } My_Shared_Ptr& operator =(My_Shared_Ptr&& other) noexcept { if (this != &other) { if (_RefBlock && _RefBlock->Decref () == 0 ) { _Deleter(_Ptr); delete _RefBlock; } _Ptr = other._Ptr; _RefBlock = other._RefBlock; other._Ptr = nullptr ; other._RefBlock = nullptr ; } return *this ; } long use_count () const { return _RefBlock ? _RefBlock->use_count () : 0 ; } pointer get () const { return _Ptr; } void reset (pointer p = nullptr ) { if (_RefBlock && _RefBlock->Decref () == 0 ) { _Deleter(_Ptr); delete _RefBlock; } _Ptr = p; _RefBlock = p ? new My_RefCount <_Ty>(p) : nullptr ; } void swap (My_Shared_Ptr& other) { std::swap (_Ptr, other._Ptr); std::swap (_RefBlock, other._RefBlock); } explicit operator bool () const { return _Ptr != nullptr ; } _Ty& operator *() const { return *_Ptr; } pointer operator ->() const { return _Ptr; } };
辅助函数与语义 辅助函数
get() :返回内部裸指针,不转移所有权,严禁手动 delete 该指针;
reset() :释放当前资源,引用计数-1 ,可传入新指针重新绑定,无参数则置空;
use_count() :返回当前强引用计数值,多用于调试排查生命周期问题;
swap() :交换两个 shared_ptr 的资源与控制块,无拷贝、无释放,效率极高;
operator bool() :隐式布尔转换,快速判断指针是否持有有效资源;
无 release()接口 :与 unique_ptr 区别,不支持手动释放所有权,避免引用计数混乱。
拷贝与移动语义实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { shared_ptr<Int> pa (new Int(1 )) ; shared_ptr<Int> pb (pa) ; shared_ptr<Int> pc (move(pa)) ; shared_ptr<Int> pd; pd = pb; shared_ptr<Int> pe; pe = move (pd); return 0 ; }
进阶工程用法 动态数组管理 shared_ptr 支持数组特化版本,使用时需指定数组类型,自动调用 delete[]删除器, C++17 及以上版本支持 make_shared 直接创建数组,严禁用普通 shared_ptr 管理动态数组,否则会因释放方式不匹配导致程序崩溃。
1 2 3 4 5 6 7 shared_ptr<Int[]> pArr (new Int[5 ]) ;shared_ptr<Int[]> pArr2 = make_shared <Int[]>(5 ); shared_ptr<Int> pErr (new Int[5 ]) ;
与 STL 容器结合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <vector> #include <list> #include <memory> int main () { list<shared_ptr<Int>> intList; intList.emplace_back (make_shared <Int>(12 )); intList.emplace_back (make_shared <Int>(23 )); vector<shared_ptr<Int>> intVec; intVec.emplace_back (make_shared <Int>(34 )); intVec.emplace_back (make_shared <Int>(45 )); return 0 ; }
多态与智能指针类型转换 shared_ptr 原生支持多态特性,标准库提供专属类型转换函数,替代普通指针的强制转换,同步保障引用计数正常联动,避免生命周期管控异常,核心转换函数如下:
static_pointer_cast :静态转换,适配派生类转基类(上行转换);
dynamic_pointer_cast :动态转换,适配基类转派生类(下行转换),自带安全校验;
const_pointer_cast :移除 const 属性,对应 const_cast 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Animal {public : virtual void eat () = 0 ; virtual ~Animal () = default ; }; class Dog : public Animal {public : void eat () override { cout << "Dog eat bone" << endl; } }; int main () { shared_ptr<Dog> pd = make_shared <Dog>(); shared_ptr<Animal> pa = pd; pa->eat (); shared_ptr<Dog> pd2 = dynamic_pointer_cast <Dog>(pa); if (pd2) pd2->eat (); return 0 ; }
工厂方法模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Shape {public : virtual void draw () = 0 ; virtual ~Shape () = default ; static shared_ptr<Shape> factory (const string& type) ; }; class Circle : public Shape {public : void draw () override { cout << "Draw Circle" << endl; } }; shared_ptr<Shape> Shape::factory (const string& type) { if (type == "Circle" ) return make_shared <Circle>(); return nullptr ; } int main () { vector<shared_ptr<Shape>> shapes; shapes.emplace_back (Shape::factory ("Circle" )); for (auto & ptr : shapes) ptr->draw (); return 0 ; }
隐患:循环引用 循环引用是 shared_ptr 较为常见的内存泄漏诱因,具体指两个或多个对象通过 shared_ptr 相互持有对方,形成闭环引用关系,导致双方引用计数永远无法归零,资源无法正常释放,这类问题需要搭配 weak_ptr 弱引用破除闭环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class A ;class B ;class A {public : shared_ptr<B> pb; ~A () { cout << "Destroy A" << endl; } }; class B {public : shared_ptr<A> pa; ~B () { cout << "Destroy B" << endl; } }; int main () { shared_ptr<A> a = make_shared <A>(); shared_ptr<B> b = make_shared <B>(); a->pb = b; b->pa = a; return 0 ; }
线程安全说明
引用计数线程安全 :计数增减采用原子操作,多线程并发拷贝、析构 shared_ptr ,计数不会错乱;
托管对象非线程安全 :多线程同时读写托管对象,需搭配互斥锁,避免数据竞争;
单实例指针非线程安全 :多线程同时修改同一个 shared_ptr 实例(赋值、 reset ),需加锁保护。
高频易错禁忌
避免同一裸指针初始化多个 shared_ptr ,防止重复释放崩溃;
留意循环引用问题,可通过 weak_ptr 弱引用替代强引用破除闭环;
不混用 shared_ptr 与裸指针管理同一份资源;
不手动 delete get()返回的裸指针,避免破坏引用计数逻辑;
仅管理堆对象,不使用 shared_ptr 托管栈对象。
适用场景
多对象、多模块需要共享同一份资源,且无法确定唯一释放时机的场景;
STL 容器中存储智能指针,替代裸指针与废弃的 auto_ptr ;
多态对象管理、工厂模式返回值,兼顾生命周期安全与多态特性;
多线程资源共享(搭配互斥锁保障对象访问安全);
资源需要被多次引用、传递,且无需手动管控释放的场景。
循环引用是 shared_ptr 在工程中常见的内存泄漏场景,两个对象通过 shared_ptr 相互持有形成闭环后,双方引用计数无法减至 0 ,资源无法释放,搭配 weak_ptr 即可解决这类问题,具体用法详见下方 weak_ptr 章节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class A ;class B ;class A {public : shared_ptr<B> pb; ~A () { cout << "Destroy A" << endl; } }; class B {public : shared_ptr<A> pa; ~B () { cout << "Destroy B" << endl; } }; int main () { shared_ptr<A> a = make_shared <A>(); shared_ptr<B> b = make_shared <B>(); a->pb = b; b->pa = a; return 0 ; }
其常用接口功能清晰直观: use_count 用于获取当前对象的引用计数值, unique 判断当前指针是否单独持有托管对象, reset 重置指针并自动释放原有关联资源, get 用于获取内部裸指针(不建议手动修改或提前释放该指针,避免破坏智能指针生命周期管控)。