题目
编写程序,演示共享所有权智能指针 std::shared_ptr 的内部引用计数(Reference Counting)控制块机制。观察当对象被多个 shared_ptr 拷贝共享时 use_count() 的动态递增,以及各个指针离开作用域时计数的递减;剖析 std::make_shared 将托管对象与控制块合并单次内存分配(Single Allocation)相对于裸 new 触发两次离散堆分配的缓存局部性优化。
输入格式:无需外部输入。
输出格式:输出引用计数逐步增减、对象最终析构与指针共享的日志。
数据范围与约定:共享所有权、控制块(Control Block)结构与原子引用计数。
思路与算法
1. 控制块内部结构std::shared_ptr 实例本身包含两个指针(大小 16 字节):一个指向托管对象,另一个指向堆上的控制块。控制块内维护强引用计数(Strong Ref)、弱引用计数(Weak Ref)及自定义删除器。
2. make_shared 的性能红利
写 shared_ptr<T>(new T) 需要触发 2 次堆内存分配(1 次给 T,1 次给控制块);而 make_shared<T> 在单块连续堆内存中同时容纳 T 和控制块,不仅节省 1 次 malloc 开销,还极大提升了 CPU 缓存行命中率。
3. 线程安全界限
引用计数的增减是原子操作(std::atomic),因而是线程安全的;但指向的目标对象本身的读写如果跨线程访问,仍需业务层加锁保护。
完整程序
#include <iostream>
#include <memory>
#include <string>
class SharedNode {
public:
int id;
explicit SharedNode(int i) : id(i) {
std::cout << " [构造 SharedNode] id = " << id << "\n";
}
~SharedNode() {
std::cout << " [析构 SharedNode] id = " << id << " (引用计数归零,彻底释放)\n";
}
};
int main() {
std::cout << "shared_ptr 实例本身占用栈空间: " << sizeof(std::shared_ptr<SharedNode>)
<< " 字节 (包含目标指针与控制块指针)\n\n";
// 1. 使用 make_shared 创建共享实例
std::cout << "--- 1. 创建 sp1 ---\n";
std::shared_ptr<SharedNode> sp1 = std::make_shared<SharedNode>(999);
std::cout << "sp1 初始引用计数 use_count(): " << sp1.use_count() << "\n\n";
// 2. 拷贝构造共享指针,引用计数递增
{
std::cout << "--- 2. 进入局部作用域,创建 sp2 与 sp3 ---\n";
std::shared_ptr<SharedNode> sp2 = sp1;
std::shared_ptr<SharedNode> sp3 = sp2;
std::cout << "拷贝共享后当前引用计数 use_count(): " << sp1.use_count()
<< " (sp1, sp2, sp3 共同共享所有权)\n";
std::cout << "离开局部作用域,sp2 和 sp3 即将销毁...\n";
}
// 3. 局部指针析构后,计数回落
std::cout << "\n--- 3. 离开局部作用域后 ---\n";
std::cout << "当前引用计数 use_count(): " << sp1.use_count() << " (对象依然存活)\n\n";
// 4. 重置主指针,计数归零触发析构
std::cout << "--- 4. 重置 sp1.reset() ---\n";
sp1.reset();
std::cout << "程序即将退出\n";
return 0;
}运行示例
输出:
shared_ptr 实例本身占用栈空间: 16 字节 (包含目标指针与控制块指针)
--- 1. 创建 sp1 ---
[构造 SharedNode] id = 999
sp1 初始引用计数 use_count(): 1
--- 2. 进入局部作用域,创建 sp2 与 sp3 ---
拷贝共享后当前引用计数 use_count(): 3 (sp1, sp2, sp3 共同共享所有权)
离开局部作用域,sp2 和 sp3 即将销毁...
--- 3. 离开局部作用域后 ---
当前引用计数 use_count(): 1 (对象依然存活)
--- 4. 重置 sp1.reset() ---
[析构 SharedNode] id = 999 (引用计数归零,彻底释放)
程序即将退出