引言
上一篇我们学习了 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。