C++ 学习笔记(七):智能指针——unique_ptr 与 shared_ptr
裸指针本身不表达所有权——看到一个 int *p,你不知道它是谁分配的、谁负责释放、释放了没有。智能指针把所有权语义编进类型里,让编译器帮你管理内存。
一、unique_ptr:独占所有权
unique_ptr 表示”这块内存只有我持有,我析构时自动释放”。
1 |
|
独占的意思是不能拷贝,只能移动:
1 | auto a = std::make_unique<int>(42); |
转移后 a 变为空,不再持有任何资源,析构时什么都不做。
从函数返回 unique_ptr
1 | std::unique_ptr<SensorDriver> create_sensor(uint8_t addr) { |
传给函数
1 | // 转移所有权:函数接管资源 |
自定义删除器
unique_ptr 可以指定资源释放方式,不只是 delete:
1 | // 用 free 释放 malloc 分配的内存 |
二、shared_ptr:共享所有权
有时候一块资源需要被多个对象共同持有,最后一个持有者离开时才释放。shared_ptr 用引用计数实现这个语义:
1 | auto a = std::make_shared<SensorData>(); |
shared_ptr 可以拷贝,拷贝时引用计数加一:
1 | std::shared_ptr<SensorData> global_data; |
引用计数的开销
- 每个
shared_ptr对象有两个指针:一个指向资源,一个指向控制块(存放引用计数) - 引用计数的增减是原子操作,多线程安全,但有 CPU 开销
- 控制块是额外的堆分配,
make_shared把资源和控制块合并成一次分配,效率更高
三、weak_ptr:打破循环引用
shared_ptr 互相持有对方时,引用计数永远不归零:
1 | struct Node { |
weak_ptr 持有弱引用,不增加引用计数:
1 | struct Node { |
使用 weak_ptr 时需要先升级为 shared_ptr,升级失败说明资源已经释放:
1 | std::weak_ptr<SensorData> weak = shared_data; |
四、如何选择
| 场景 | 用哪个 |
|---|---|
| 独占所有权,单一持有者 | unique_ptr |
| 共享所有权,多个持有者 | shared_ptr |
| 观察但不持有,避免循环引用 | weak_ptr |
| 性能敏感、确定生命周期 | 裸指针(借用语义) |
优先用 unique_ptr——它的开销和裸指针完全一样,只是多了析构时的 delete,没有任何运行时额外开销。shared_ptr 有引用计数的原子操作开销,只在真正需要共享所有权时才用。
五、嵌入式里的取舍
Linux 嵌入式:智能指针开箱即用,和 PC 开发完全一样,unique_ptr 几乎可以全面替代 new/delete。
裸机 MCU:
- 本来就尽量避免动态内存,智能指针用得少
unique_ptr零开销,在需要动态分配的场合可以用shared_ptr的控制块需要额外堆分配,加上原子操作,在没有 OS 的环境里性价比不高- 如果禁用了 RTTI 和异常(
-fno-rtti -fno-exceptions),shared_ptr的部分实现可能有问题,取决于工具链
实际上裸机项目里最常见的用法是:对象静态分配,用裸指针传递借用语义,不涉及所有权转移;只在确实需要动态生命周期的地方用 unique_ptr。
总结
unique_ptr:独占所有权,不可拷贝只能移动,零运行时开销,优先使用shared_ptr:共享所有权,引用计数管理生命周期,有原子操作开销weak_ptr:弱引用,不增加引用计数,用于打破循环引用或观察资源make_unique/make_shared:比直接new更安全,make_shared还能减少一次堆分配- 裸指针不消失:借用语义(不转移所有权)继续用裸指针或引用,清晰且高效
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Wayson's Blog!
评论




