前言
-
智能指针是可以自动回收内存的指针
-
共享智能指针
shared_ptr
,可以多个共享同一块堆内存独享智能指针
unique_ptr
,只能独有一块堆内存弱引用智能指针
weak_ptr
,也是一种共享型,可以视作标准库给shared_ptr
打的补丁 -
智能指针和裸指针不要混用
-
本文使用 Visual Studio 2022 进行测试
shared_ptr
可以给堆内存加一个引用计数,即指向它的指针数,如果为 0 就释放
构造
-
用地址初始化(
new
表达式,指针)- 注:
- 不建议用指针初始化,因为智能指针自动释放,如果手动释放了指针会导致重复释放
- 简单说,当使用智能指针时,不允许操控其内部的指针本体
- 参数支持地址,但你可千万别把地址当参数,拿内存当玩具
- 不建议用指针初始化,因为智能指针自动释放,如果手动释放了指针会导致重复释放
std::shared_ptr<int> shared_i_ptr(new int(100));
- 注:
-
用专门实现的
make_shared
,更高效std::shared_ptr<int> shared_i_ptr = std::make_shared<int>(100);
-
拷贝构造函数(用于共享)
引用计数
use_count()
显示当前与自己指向同一个目标的shared_ptr
总数
reset()
无参数表示取消对该堆内存的引用,会修改引用计数,所以不能在 const 条件下调用
std::shared_ptr<int> sharedI1 = std::make_shared<int>(100);
std::shared_ptr<int> sharedI2(sharedI1);
std::cout << sharedI1.use_count(); // 2
sharedI1.reset();
std::cout << sharedI1.use_count(); // 1
实现传统指针操作
cout << *sharedI1 << endl;
sharedP -> func(); // 假设有成员函数
其他函数
sharedP.unique(); // 是否独占
sharedP.reset(地址); // 改变指针指向和原对象的引用计数,无参则置空,因为底层是2个指针所以细节略有说法
sharedP = sharedP2; // 也可以赋值改变指向
sharedI.swap(sharedI2); // 交换指向内存,推荐用,问就是标准库实现的,有优化
std::swap(sharedI, sharedI2); // 等效的宏提供的函数
sharedP.get(); // 返回原始指针,只是保留访问,但非必要情况都默认废止了
其他语法
-
指向数组,也是少数需要借助原始指针的场合(不清楚 C++17/20 后的标准有没有改)
std::shared_ptr<int> shared(new int[100]()); shared.get()[5]; // 获取指针再[ ],暂不支持直接使用[ ]
-
作为参数,
shared_ptr
固定为 2 个指针大小(32/64 位系统中分别占 8/16 字节)所以可以直接传值关于
shared_ptr
为何是 2 个指针,以下是来自 ChatGPT 的解释:- 内存指针:
- 内存指针指向实际的动态分配内存,其中存储着数据或对象。
- 它是
shared_ptr
的主要指针,用于管理实际的资源。当shared_ptr
创建时,它会持有对这个内存的引用,允许你访问和操作内存中的数据。 - 内存指针通常是你最关心的部分,因为它指向你要管理的数据或对象。
- 控制块指针:
- 控制块指针指向一个控制块(reference control block),这个控制块包含有关所指内存的元数据和引用计数信息。
- 控制块用于管理共享相同内存的所有
shared_ptr
实例。它通常包含以下信息:- 引用计数:表示有多少个
shared_ptr
实例共享相同的内存。 - 指向内存的指针:实际指向内存的指针。
- 自定义删除器:用于在引用计数归零时释放内存的自定义函数对象(如果提供了)。
- 其他元数据:如类型信息等。
- 引用计数:表示有多少个
-
控制块起到管理和协调作用,确保在多个
shared_ptr
实例之间正确跟踪引用计数和在引用计数归零时释放内存。这使得多个shared_ptr
实例可以共享相同的内存,而不会引起资源泄漏,因为内存的释放是由控制块来处理的。 -
所以,总结来说,内存指针是直接指向实际资源的指针,而控制块指针指向一个控制块,这个控制块负责跟踪共享资源的元数据和引用计数信息。这种组合允许
shared_ptr
实现了内存资源的共享和自动释放,以确保资源安全地管理。
- 内存指针:
weak_ptr
专用于弥补
shared_ptr
缺陷(循环引用)的指针,特点是不会影响引用计数
构造和计数
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
std::cout << sharedI.use_count() << std::endl; // 1
std::weak_ptr<int> weakI(sharedI);
std::cout << weakI.use_count() << std::endl; // 1
**shared_ptr 的循环引用:**假设堆上分配了两个互相引用的
shared_ptr
,栈上指针销毁后,堆上指针引用计数仍为 1,造成泄漏class B; // C++ 特有的编译需要先有声明,成员变量使用指针也是因为指针大小固定,不需要先编译类来确定大小 class A{ public: std::shared_ptr<B> sharedB; // 可以改成 weak_ptr<B> }; class B{ public: std::shared_ptr<A> sharedA; }; int main(){ std::shared_ptr<A> shareA = std::make_shared<A>(); std::shared_ptr<B> shareB = std::make_shared<B>(); shareA->sharedB = shareB; shareB->sharedA = shareA; // 两块堆内存互相指 } // 栈指针销毁,堆内存计数始终为1,彼此都无法释放,泄漏!
因此设计了一种不会影响引用计数的
weak_ptr
,用且几乎仅用于此类情况对于以上情况,将至少一个堆对象成员指针换成
weak_ptr
,能保证栈指针销毁后,其中一块堆内存计数为 0
此外,weak_ptr
的使用基本等同 shared_ptr
unique_ptr
独占特化的智能指针,当对象不需要被多个指针共享时,使用
unique_ptr
可以降低消耗因为不需要考虑共享和计数问题,所以
unique_ptr
只有一个指针
unique_ptr
禁止了拷贝构造和赋值运算符,因为破坏了独占
unique_ptr
允许移动构造和赋值,因为移动没有影响独占std::unique_ptr<int> uniqueI = std::make_unique<int>(100); std::unique_ptr<int> uniqueI2 = std::move(uniqueI);
unique_ptr
可以作为右值转为shared_ptr
,反之共享则不能转为独占。这个属于是提供容错用的,不用管std::shared_ptr<int> sharedI = std::make_unique<int>(100);
如何上手智能指针
省流:优先级
unique_ptr
→shared_ptr
→weak_ptr
→ 传统指针
-
除了以下情况,都可以用智能指针,因为本身就是为了让指针这远古玩意跟上新版本而加的套壳
-
规定使用 C 指针的函数:
-
网络传输函数,如 Windows 下的
send
,recv
等 -
C 语言库函数,如文件操作(但如今支持智能指针的 C++ 函数也在平替中)
-
-
指向数组:
智能指针对数组的支持并不好,指操作不便
-
-
优先使用
unique_ptr
需要共享时使用
shared_ptr
在此基础上如果循环引用,则改为
weak_ptr