背景:在C++中,手动管理资源(new/delete)容易出错,导致:内存泄漏,重复释放,异常安全等问题。
为了解决这些问题,C++11引入了智能指针:
std::unique_ptr:独占所有权,std::shared_ptr 共享所有权,通过引用计数来管理生命周期
原理:
unique_ptr:目标是独占所有权,不可复制,只能移动(move)
使用场景:资源唯一所有权,RAII风格自动释放;
核心问题是:避免内存泄漏,支持移动语义。
核心原理:
1.独占所有权:唯一指针拥有资源,拷贝被禁用
2.移动语义:通过移动实现资源转移:
3.RAII:生命周期结束自动释放。
3.性能特点:
内存占用小(只存储原始指针和删除器)
无额外引用计数开销
编译器开销低
异常安全,必变资源泄漏;
shared_ptr:目标是多方共享资源,引用计数控制生命周期。
使用场景:多线程共享对象,缓存管理。
核心问题:
自动管理生命周期;处理多线程环境下的引用计数(atomic)
避免循环引用问题
1.内部结构:核心在于引用计数和控制块。
shared_ptr<T>
├── T* ptr // 实际对象指针
└── ControlBlock* cb // 引用计数和删除器
控制块结构示例:
2.核心逻辑:
1)构造shared_ptr:初始化use_count = 1;
- 拷贝shared_ptr:use_count++(原子操作)
3)析构shared_ptr:use_count--,若为0调用删除器销毁对象;
weak_ptr:不增加use_count,仅增加weak_count,用于解决循环引用。
3.关键实现细节:
线程安全:std::atomic 保证引用计数在多线程下正确;
控制块分离:
对象和控制快分离,可以支持make_shared内联分配(减少内存碎片)
循环引用问题:
两个shared_ptr互相循环引用会导致引用计数不为0
需要weak_ptr解决;
4.性能分析:
unique_ptr vs shared_ptr

1.内存占用:
小,指针+删除器; 控制块额外开销,atomic计数
- 拷贝/移动开销
禁止拷贝,移动开销小; 拷贝需要原子操作,移动开销小
3.多线程安全
依赖外部保护; 引用计数原子操作保证安全
关键区别:引用计数的原子性
cpp
cpp
// ❌ unique_ptr - 所有权转移不是线程安全的
class BadExample {
std::unique_ptr<Data> data;
public:
// 线程1调用
void thread1() {
auto temp = std::move(data); // 危险!没有同步
// data现在是nullptr
}
// 线程2调用
void thread2() {
if (data) { // 竞态条件!
data->process(); // 可能崩溃
}
}
};
// ✅ shared_ptr - 引用计数操作是原子的
class GoodExample {
std::shared_ptr<Data> data;
public:
// 线程1调用
void thread1() {
auto local_copy = data; // 原子地增加引用计数
if (local_copy) {
local_copy->process(); // 安全使用
}
} // 原子地减少引用计数
// 线程2调用
void thread2() {
auto local_copy = data; // 原子地增加引用计数
if (local_copy) {
local_copy->process(); // 安全使用
}
} // 原子地减少引用计数
};
更好的对比示例

cpp
// unique_ptr - 必须用锁保护所有权转移
class TaskProcessor {
std::unique_ptr<Task> current_task;
std::mutex task_mutex;
public:
void process() {
std::unique_ptr<Task> local_task;
{
std::lock_guard<std::mutex> lock(task_mutex);
local_task = std::move(current_task); // 必须在锁内移动
}
if (local_task) {
local_task->execute();
}
}
void setTask(std::unique_ptr<Task> task) {
std::lock_guard<std::mutex> lock(task_mutex); // 必须加锁
current_task = std::move(task);
}
};
// shared_ptr - 复制操作本身是线程安全的
class DataCache {
std::shared_ptr<CachedData> cached_data;
// 注意:这里不需要mutex保护shared_ptr的复制!
public:
void updateCache(std::shared_ptr<CachedData> new_data) {
cached_data = new_data; // 原子操作,无需加锁
}
std::shared_ptr<CachedData> getData() {
return cached_data; // 原子操作,无需加锁
}
void process() {
auto local_data = getData(); // 线程安全地获取副本
if (local_data) {
// 即使其他线程调用updateCache(),local_data仍然有效
local_data->doWork();
}
}
};
4.循环引用风险
无; 有,需要weak_ptr
5.适用场景:
独占资源 RAII; 多方共享对象管理,缓存,异步任务。
核心思想:能用 unique_ptr 就用 unique_ptr,能静态分析生命周期,避免 atomic 开销;需要多方共享才用 shared_ptr,并结合 weak_ptr 避免循环引用。
总结:
unique_ptr:
独占资源所有权,低开销,RAII自动释放
通过移动语义实现所有权转移
shared_ptr:共享资源,通过引用计数和控制块管理生命周期
支持多线程,但有性能开销
循环引用需要weak_ptr解决。
在分布式系统
•
hotspot 对象尽量用 unique_ptr
•
异步回调、共享上下文用 shared_ptr
一句话总结 :
"能唯一所有权就用 unique_ptr,必须共享就用 shared_ptr,控制引用计数就是控制你的性能。"
struct ControlBlock {
std::atomic<size_t> use_count; // 强引用计数
std::atomic<size_t> weak_count; // 弱引用计数
Deleter deleter; // 删除器
T* ptr; // 指向对象
};