提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一、前置准备
-
- [1. 带行号的测试代码](#1. 带行号的测试代码)
- [2. 核心规则(对应我们的 `SharedPtr` 实现)](#2. 核心规则(对应我们的
SharedPtr实现))
- 二、逐行执行解析
- 三、函数结束:析构阶段(隐式执行)
- 四、总结
结合我们自定义实现的 SharedPtr ,我将完整拆解 test_shared_ptr 测试函数的每一行执行逻辑、内存状态、引用计数变化、输出结果,并对应到类的实现原理说明原因。
一、前置准备
1. 带行号的测试代码
cpp
// 测试SharedPtr
void test_shared_ptr() {
std::cout << "\n===== 测试SharedPtr =====" << std::endl; // 行1
SharedPtr<std::string> sp1(new std::string("Hello")); // 行2
std::cout << *sp1 << ",强引用计数:" << sp1.use_count() << std::endl; // 行3
SharedPtr<std::string> sp2 = sp1; // 拷贝构造 行4
std::cout << *sp2 << ",强引用计数:" << sp1.use_count() << std::endl; // 行5
sp1.reset(new std::string("World")); // 行6
std::cout << *sp1 << ",强引用计数:" << sp1.use_count() << std::endl; // 行7
std::cout << *sp2 << ",强引用计数:" << sp2.use_count() << std::endl; // 行8
SharedPtr<std::string> sp3 = std::move(sp1); // 移动构造 行9
if (sp1.get() == nullptr) { // 行10
std::cout << "sp1移动后为空" << std::endl; // 行11
}
std::cout << *sp3 << ",强引用计数:" << sp3.use_count() << std::endl; // 行12
}
2. 核心规则(对应我们的 SharedPtr 实现)
- 构造新指针 :绑定原始指针时,创建
RefCount对象,强引用计数初始化为 1; - 拷贝构造/赋值 :共享资源和计数,强引用计数 +1;
- 移动构造/赋值 :转移所有权,计数不变化,原指针置空;
- reset()/析构 :调用
release(),强引用计数 -1;计数为0时,释放托管的堆内存; use_count():返回当前强引用计数,无资源时返回0。
二、逐行执行解析
行1:打印标题
cpp
std::cout << "\n===== 测试SharedPtr =====" << std::endl;
- 操作:控制台输出分割标题,无内存操作;
- 结果 :控制台打印
===== 测试SharedPtr =====; - 原因:标准输出流的常规打印逻辑。
行2:构造sp1,托管字符串对象
cpp
SharedPtr<std::string> sp1(new std::string("Hello"));
- 操作 :
- 在堆上创建
std::string("Hello")对象; - 调用
SharedPtr的有参构造函数,sp1托管该对象; - 新建
RefCount对象,strong_ref = 1;
- 在堆上创建
- 内存状态 :
sp1.ptr_→ 堆上的"Hello"字符串;sp1.ref_count_→ 计数对象,强引用=1;
- 结果 :
sp1成为第一个托管该字符串的智能指针; - 原因 :构造函数中,非空指针会初始化引用计数为
1,标记当前只有1个所有者。
行3:解引用并打印计数
cpp
std::cout << *sp1 << ",强引用计数:" << sp1.use_count() << std::endl;
- 操作 :
- 重载
operator*,获取sp1托管的字符串值; - 调用
use_count(),读取强引用计数;
- 重载
- 输出结果 :
Hello,强引用计数:1; - 原因 :
*sp1直接访问托管的字符串内容Hello;- 当前仅
sp1持有资源,计数为1。
行4:拷贝构造sp2,共享sp1的资源
cpp
SharedPtr<std::string> sp2 = sp1;
- 操作 :调用拷贝构造函数 ,
sp2与sp1共享同一块内存和计数对象,强引用计数 +1; - 内存状态 :
sp1、sp2的ptr_都指向"Hello";- 共享的计数对象
strong_ref = 2;
- 结果:两个智能指针共享同一个资源;
- 原因 :拷贝构造的核心逻辑是共享所有权,计数加1表示新增一个资源所有者。
行5:打印sp2的值和计数
cpp
std::cout << *sp2 << ",强引用计数:" << sp1.use_count() << std::endl;
- 输出结果 :
Hello,强引用计数:2; - 原因 :
sp2与sp1指向同一字符串,解引用结果都是Hello;- 计数对象共享,无论用
sp1还是sp2调用use_count(),结果都为2。
行6:sp1调用reset,切换托管对象
cpp
sp1.reset(new std::string("World"));
- 操作 (
reset函数逻辑):- 调用
release():原计数2 → 1,计数不为0,不释放"Hello"内存; - 在堆上创建新字符串
"World"; sp1绑定新对象,创建新的计数对象 ,强引用计数重置为1;
- 调用
- 内存状态 :
sp1:指向"World",计数=1;sp2:仍指向"Hello",计数=1;
- 结果 :
sp1与sp2彻底分离,各自托管独立资源; - 原因 :
reset会先释放当前所有权(计数减1),再接管新资源,计数归1。
行7:打印sp1新对象的信息
cpp
std::cout << *sp1 << ",强引用计数:" << sp1.use_count() << std::endl;
- 输出结果 :
World,强引用计数:1; - 原因 :
sp1已切换到新字符串World,且是该资源的唯一所有者,计数为1。
行8:打印sp2的信息
cpp
std::cout << *sp2 << ",强引用计数:" << sp2.use_count() << std::endl;
- 输出结果 :
Hello,强引用计数:1; - 原因 :
sp2未被修改,依旧持有原Hello字符串,且是唯一所有者,计数为1。
行9:移动构造sp3,转移sp1的所有权
cpp
SharedPtr<std::string> sp3 = std::move(sp1);
- 操作 :调用移动构造函数 ,将
sp1的指针、计数对象转移 给sp3;- 计数数值不发生变化;
- 原
sp1的ptr_和ref_count_置为nullptr;
- 内存状态 :
sp3:指向"World",计数=1;sp1:空指针,无计数对象;
- 结果 :所有权从
sp1转移给sp3,sp1失效; - 原因 :移动语义是所有权转移,而非共享,因此不修改引用计数,仅清空原对象。
行10~11:判断sp1是否为空并打印
cpp
if (sp1.get() == nullptr) {
std::cout << "sp1移动后为空" << std::endl;
}
- 操作 :
get()返回原始指针,判断是否为nullptr; - 输出结果 :
sp1移动后为空; - 原因 :移动构造后,
sp1的内部指针被主动置空,不再持有任何资源。
行12:打印sp3的信息
cpp
std::cout << *sp3 << ",强引用计数:" << sp3.use_count() << std::endl;
- 输出结果 :
World,强引用计数:1; - 原因 :
sp3接管了sp1的资源,是World的唯一所有者,计数保持1。
三、函数结束:析构阶段(隐式执行)
当test_shared_ptr函数执行完毕,栈上的sp1/sp2/sp3依次析构,触发SharedPtr析构函数:
sp1:已为空,无任何操作;sp2:计数1→0,释放Hello字符串内存;sp3:计数1→0,释放World字符串内存;
最终所有堆内存都被自动释放,无内存泄漏。
四、总结
- 拷贝=共享所有权:计数+1,多个指针管理同一块内存,计数为0才释放资源;
- 移动=转移所有权:计数不变,原指针置空,是更高效的所有权传递方式;
- reset=主动释放+重新绑定:先放弃旧资源(计数-1),再托管新对象;
- 引用计数是核心 :所有共享指针的行为,都围绕强引用计数的增减判断内存释放时机。