C++ 智能指针完全指南(二):shared_ptr 深度详解

引言

上一篇我们学习了 unique_ptr------独占所有权的智能指针。它解决了"忘记 delete"的问题,但它的独占特性意味着一个对象只能有一个 owner

现实中有大量场景需要共享所有权 :多个窗口共享同一个数据模型、多个线程共享同一个资源、缓存中的对象被多处引用。这时就需要 shared_ptr

shared_ptr 通过引用计数 来管理共享所有权:每多一个 shared_ptr 指向同一个对象,计数 +1;每销毁一个,计数 -1。计数归零时,对象自动释放。

第一部分:shared_ptr 的核心原理

一、引用计数

cpp 复制代码
#include <memory>
#include <iostream>
using namespace std;

int main() {
    shared_ptr<int> p1 = make_shared<int>(42);
    cout << "引用计数:" << p1.use_count() << endl;  // 1

    {
        shared_ptr<int> p2 = p1;  // p2 和 p1 共享同一个对象
        cout << "引用计数:" << p1.use_count() << endl;  // 2
        cout << "引用计数:" << p2.use_count() << endl;  // 2(相同)

        shared_ptr<int> p3 = p1;
        cout << "引用计数:" << p1.use_count() << endl;  // 3
    }  // p3 和 p2 离开作用域,引用计数 -2

    cout << "引用计数:" << p1.use_count() << endl;  // 1
}  // p1 离开作用域,引用计数归零 → 自动 delete

二、控制块

shared_ptr 不只是把裸指针包起来,它背后有一个控制块

三、创建方式

cpp 复制代码
// 方式1:make_shared(最推荐!)
auto p1 = make_shared<int>(42);

// 方式2:用 new(不推荐)
shared_ptr<int> p2(new int(42));

// 方式3:从 unique_ptr 移动
unique_ptr<int> up = make_unique<int>(42);
shared_ptr<int> p3 = std::move(up);  // up 变成 nullptr

为什么优先用 make_shared

创建方式 内存分配次数 内存布局
make_shared 1 次 对象和控制块在同一块内存中
new + shared_ptr 2 次 对象一块、控制块一块

第二部分:shared_ptr 的常用操作

一、基本操作

cpp 复制代码
auto p1 = make_shared<int>(42);

// 访问
cout << *p1 << endl;       // 解引用
*p1 = 100;                 // 修改

// 获取原始指针
int* raw = p1.get();       // 不增加引用计数,不转移所有权

// 判断是否为空
if (p1) { /* p1 不为空 */ }
if (p1 != nullptr) { /* 等价 */ }

// 引用计数
cout << p1.use_count() << endl;

// 是否是唯一持有者
if (p1.unique()) { /* 引用计数 == 1 */ }

// 释放
p1.reset();                // 引用计数 -1,归零则释放对象
p1.reset(new int(200));    // 释放旧对象,接管新对象

二、作为函数参数

cpp 复制代码
// ✅ 值传递:增加引用计数
void func(shared_ptr<int> p) {
    cout << p.use_count() << endl;  // 2(调用方的 1 + 这里的 1)
}

// ✅ const 引用:不增加引用计数,只读
void func2(const shared_ptr<int>& p) {
    cout << *p << endl;
}

// ✅ 如果只需要对象本身,传引用就行
void func3(int& value) {
    value++;
}

int main() {
    auto p = make_shared<int>(42);
    func(p);     // 引用计数 +1 然后 -1
    func2(p);    // 引用计数不变
    func3(*p);   // 直接操作对象,不涉及 shared_ptr
}

第三部分:shared_ptr 的线程安全性

cpp 复制代码
#include <mutex>
#include <thread>

auto p = make_shared<int>(0);
mutex mtx;

// ✅ shared_ptr 本身可以被多个线程安全拷贝
thread t1([p] {             // 引用计数 +1
    lock_guard<mutex> lock(mtx);
    (*p)++;                 // 保护对象操作
});

thread t2([p] {             // 引用计数 +1
    lock_guard<mutex> lock(mtx);
    (*p)++;
});

// ✅ 引用计数正确 = 3(主线程1 + t1 + t2)

第四部分:自定义删除器

cpp 复制代码
// 管理 FILE*
auto fileDeleter = [](FILE* f) {
    if (f) fclose(f);
};

shared_ptr<FILE> filePtr(fopen("test.txt", "w"), fileDeleter);
fprintf(filePtr.get(), "hello\n");
// 离开作用域 → fclose 自动调用

和 unique_ptr 的区别shared_ptr 的自定义删除器存储在控制块中,不同类型删除器的 shared_ptr 可以互相赋值

cpp 复制代码
shared_ptr<FILE> p1(fopen("a.txt", "r"), fclose);
shared_ptr<FILE> p2 = p1;  // ✅ 删除器会被正确共享

第五部分:enable_shared_from_this

一、问题场景

cpp 复制代码
class Widget {
public:
    shared_ptr<Widget> getSharedPtr() {
        // ❌ 错误!每次都创建新的控制块,引用计数独立!
        return shared_ptr<Widget>(this);
    }
};

auto p1 = make_shared<Widget>();
auto p2 = p1->getSharedPtr();  // p1 和 p2 各有独立的引用计数!
// p1 释放 → 对象被 delete
// p2 释放 → 再次 delete → 崩溃!

二、解决方案

cpp 复制代码
class Widget : public enable_shared_from_this<Widget> {
public:
    shared_ptr<Widget> getSharedPtr() {
        return shared_from_this();  // ✅ 正确,复用已有的控制块
    }
};

auto p1 = make_shared<Widget>();
auto p2 = p1->getSharedPtr();  // p1 和 p2 共享同一个控制块
cout << p1.use_count() << endl;  // 2

必须已经有 shared_ptr 管理该对象 ,否则 shared_from_this() 会抛异常。


第六部分:常见错误

cpp 复制代码
// ❌ 错误1:把同一个裸指针给多个 shared_ptr
int* raw = new int(42);
shared_ptr<int> p1(raw);        // 引用计数 = 1
shared_ptr<int> p2(raw);        // 新的引用计数 = 1,不是 2!
// p1 释放 → delete raw
// p2 释放 → 再次 delete raw → 崩溃!

// ✅ 正确:用拷贝
auto p1 = make_shared<int>(42);
auto p2 = p1;  // 引用计数 = 2

// ❌ 错误2:在不需要的时候传值
void func(shared_ptr<int> p) { ... }  // 每次调用引用计数 +1/-1
// ✅ 传引用(不改变所有权时)
void func(const shared_ptr<int>& p) { ... }
// ✅ 传裸引用(只需要对象本身时)
void func(int& v) { ... }

总结

一、核心要点

特性 说明
所有权 共享,多个 shared_ptr 指向同一对象
创建 make_shared<T>(args)
引用计数 use_count(),计数归零自动释放
线程安全 引用计数安全,对象操作需要额外加锁
自定义删除器 存在控制块中,可以共享
enable_shared_from_this this 安全创建 shared_ptr
头文件 <memory>

二、unique_ptr vs shared_ptr

对比 unique_ptr shared_ptr
所有权 独占 共享
大小 8 字节(无删除器) 16 字节
性能 原始指针级别 引用计数有开销
创建 make_unique make_shared
拷贝
使用场景 明确单一 owner 多个 owner

三、一句话记忆

shared_ptr 用引用计数实现共享所有权,计数归零自动释放。make_shared 一次分配省内存,引用计数本身线程安全但对象操作需加锁。从 this 创建 shared_ptr 需继承 enable_shared_from_this

相关推荐
@Ma1 小时前
Python 实现企业微信外部群主动消息发送及成功接入后如何避坑,避免风控封号
开发语言·python·企业微信
WWW65261 小时前
代码随想录 打卡第五十四天
数据结构·c++·算法
DA02211 小时前
01-Python-数据类型和语法
开发语言·python
周末也要写八哥2 小时前
线程的生命周期之“守护“线程
java·开发语言·jvm
redaijufeng2 小时前
我在C++中深入理解了继承,收获颇丰
java·c++·算法
.千余2 小时前
【C++】C++继承入门(上):继承语法与基本特性详解
开发语言·c++·笔记·学习·其他
TPBoreas2 小时前
前端面试问题打把-场景题
开发语言·前端·javascript
skywalk81632 小时前
段言的设计文档:中文编程赛道的竞争格局,谁在牌桌上?
开发语言·学习·编程