题目

编写程序,演示共享所有权智能指针 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 (引用计数归零,彻底释放)
程序即将退出