头文件:<memory>,核心思想:RAII,自动管理堆内存,避免内存泄漏。
一、auto_ptr(C++98,已废弃,不要使用)
特点:拷贝时转移资源所有权。
auto_ptr<Date> p1(new Date);
auto_ptr<Date> p2 = p1; // p1把资源管理权转移给p2,p1变成空指针
缺陷:拷贝后原指针悬空,极易踩坑。C++11 后被淘汰,工程里禁止使用。
二、unique_ptr 唯一指针(C++11)
核心:不支持拷贝,只支持移动 ,同一时刻只能有一个unique_ptr持有资源。
unique_ptr<Date> u1(new Date);
// unique_ptr<Date> u2 = u1; // 编译报错,拷贝构造被delete
unique_ptr<Date> u3 = move(u1); // 移动,u1置空,u3接管资源
-
原理:把拷贝构造、赋值重载函数 delete,禁用拷贝;
-
适用场景:资源不需要共享,独占所有权,性能开销几乎和裸指针一样;
-
支持自定义删除器。
三、shared_ptr 共享指针(C++11)
核心:引用计数,支持拷贝 ,多个shared_ptr共同管理同一块堆资源。
shared_ptr<Date> sp1(new Date);
shared_ptr<Date> sp2 = sp1; // 拷贝,引用计数+1
底层结构两块内存:
-
_ptr:指向管理的资源; -
_pcount:引用计数(堆上的 int)。
析构逻辑:
-
拷贝:引用计数
++; -
析构:引用计数
--; -
当计数减到 0 时,
delete释放资源 + 释放计数内存。
make_shared推荐优先使用:
auto sp = make_shared<Date>(); // 一次性分配资源+计数内存,效率更高
注意:
shared_ptr构造函数是explicit,不能隐式类型转换。
shared_ptr 的致命问题:循环引用
双向链表场景:节点 A 存next指向 B,节点 B 存prev指向 A,都用shared_ptr。 两个对象析构时,双方引用计数都只能减到 1,永远不会等于 0,内存泄漏。
n1 -> n2
n2 -> n1
两个节点互相持有shared_ptr,资源无法释放
👉 解决方案:weak_ptr。
四、weak_ptr 弱指针(C++11)
不参与资源所有权,不增加引用计数 ,专门用来解决shared_ptr循环引用。
-
不能直接访问资源,没有 RAII;
-
只能绑定到
shared_ptr; -
调用
.lock():返回一个shared_ptr,检查资源是否存活 。如果资源已经释放,返回空shared_ptr。
改造双向链表: 把next/prev从shared_ptr改成weak_ptr。 互相引用时不会增加引用计数,对象析构后计数可以正常减到 0,内存正常释放。
struct Node
{
int val;
weak_ptr<Node> next;
weak_ptr<Node> prev;
};
五、通用要点
-
智能指针析构默认调用
delete释放资源,支持自定义删除器,可以管理数组、文件句柄等; -
shared_ptr/unique_ptr支持bool隐式转换:if(sp)判断是否持有有效资源; -
不要把裸指针同时交给多个
shared_ptr管理,会造成重复释放。
总结选型
|------------|----------------|-------|--------------------|
| 智能指针 | 拷贝 | 核心原理 | 适用场景 |
| auto_ptr | 转移所有权 | 废弃 | 不要用 |
| unique_ptr | ❌禁止拷贝 ✅支持 move | 独占资源 | 独占所有权,默认首选 |
| shared_ptr | ✅支持拷贝 | 引用计数 | 需要多指针共享资源 |
| weak_ptr | 不持有资源 | 不增加计数 | 解决 shared_ptr 循环引用 |
编码建议:优先用
unique_ptr;确需共享再选shared_ptr;存在双向引用 / 环形结构,配合weak_ptr。