代码仓库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用于获取内部裸指针(不建议手动修改或提前释放该指针,避免破坏智能指针生命周期管控)。