C++ 智能指针:用 RAII 让堆内存"自动归还"
头文件:#include <memory>
智能指针的核心目的:自动管理堆内存,离开作用域自动释放,杜绝内存泄漏。底层原理:RAII(Resource Acquisition Is Initialization,资源获取即初始化)——构造时拿到资源,析构时释放资源。
裸指针的痛点:
new出来的内存忘delete→ 内存泄漏;重复delete;野指针。智能指针把delete写进了析构函数。
一、三大智能指针总览
| 类型 | 所有权模型 | 拷贝 | 底层开销 | 用途 |
|---|---|---|---|---|
unique_ptr |
独占 | ❌ 不可拷贝,只能 move |
极小(≈裸指针) | 绝大多数场景首选 |
shared_ptr |
共享(引用计数) | ✅ | 原子计数,开销大 | 需要多个指针共用一块内存 |
weak_ptr |
弱引用,不拥有 | ✅ | 很小 | 配合 shared_ptr 解决循环引用 |
二、unique_ptr:独占智能指针
一块堆内存,只能有一个 unique_ptr 指向它:不能拷贝,只能移动。
1 |
|
特点
- 独占,开销极小,几乎等于裸指针;
- 不能拷贝,
std::move转移所有权; - 离开作用域自动
delete; reset():释放资源;get():拿到底层裸指针(不释放所有权)。
优先选
unique_ptr——不需要共享就别用shared_ptr。
三、shared_ptr:共享智能指针
多个 shared_ptr 可指向同一块内存,内部维护引用计数。每多一个指向这块内存的 shared_ptr,计数 +1;指针销毁,计数 −1;计数降到 0 才真正释放堆内存。
1 |
|
常用接口
.use_count():获取当前引用计数.reset():释放当前对象,引用计数 −1.get():获取底层裸指针- 支持
->和*解引用
缺点
- 引用计数需要原子操作,有性能开销。
- 致命坑:循环引用,内存永远不会释放!
循环引用示例(内存泄漏)
1 | struct A { |
解决办法:用
weak_ptr替换其中一个shared_ptr。
四、weak_ptr:弱智能指针
weak_ptr 不增加引用计数,不拥有资源,只是「观察者」。专门配合 shared_ptr 解决循环引用。不能直接 -> 解引用,需要用 .lock() 尝试提升为 shared_ptr。
1 | struct A { |
常用接口
.lock():返回shared_ptr,如果对象已销毁,返回空shared_ptr.expired():判断资源是否已经释放(过期)
五、enable_shared_from_this(补充)
如果类里面想拿到指向自身的 shared_ptr,继承 enable_shared_from_this<T>,调用 shared_from_this()。
1 | struct Node : enable_shared_from_this<Node> { |
六、高频坑点(竞赛 / 面试必考)
6.1 不要用裸指针直接初始化多个 shared_ptr
1 | int* raw = new int(10); |
✅ 正确:auto a = make_shared<int>(10); auto b = a;
6.2 优先 make_shared / make_unique,而不是 new
make_shared:一次性分配「对象 + 控制块」,减少内存碎片;make_unique:C++14 起支持;相比unique_ptr<T>(new T)更安全(异常时不会泄漏)。
6.3 unique_ptr 管理数组
1 | unique_ptr<int[]> arr(new int[10]); // 析构自动 delete[] |
shared_ptr管理数组需要自定义删除器,比较麻烦。
6.4 不要把 this 直接存入 shared_ptr
要用 enable_shared_from_this<T>,否则两个 shared_ptr 各自持有一份「控制块 + 指针」,析构时同一块内存被 delete 两次。
七、极简速记
| 指针 | 选它的理由 |
|---|---|
unique_ptr |
默认选它,独占、零开销;不确定选谁就选它 |
shared_ptr |
真的需要多个所有者共享一块资源时 |
weak_ptr |
用来打破 shared_ptr 循环引用 |
make_shared / make_unique |
创建时永远首选,效率高、更安全 |
八、小结
- 智能指针把
delete写进析构函数,离开作用域自动释放,从根源上解决内存泄漏; - 优先
unique_ptr,不共享就不上shared_ptr; - 循环引用用
weak_ptr打破; - 创建永远用
make_*,别用裸new。