C++智能指针完全指南:unique_ptr、shared_ptr、weak_ptr
C++智能指针完全指南:unique_ptr、shared_ptr、weak_ptr 适用场景深度解析
一、为什么需要智能指针?
传统C++中,手动管理动态内存容易导致:
内存泄漏:忘记delete
悬空指针:释放后继续使用
二次释放:多个指针指向同一块内存
智能指针通过RAII机制自动管理资源生命周期,从C++11起成为现代C++标配。
二、unique_ptr——独占所有权
核心特性
独占所有权,不可拷贝
轻量级,性能接近裸指针
可移动转移所有权
默认使用delete释放,支持自定义删除器
适用场景
✅ 工厂函数返回对象
std::unique_ptr<Widget> createWidget() { return std::make_unique<Widget>(); }✅ 类成员变量(独占资源)
class DatabaseConnection { std::unique_ptr<Connection> conn_; public: DatabaseConnection() : conn_(std::make_unique<Connection>()) {} };✅ 容器存储多态对象
std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>()); shapes.push_back(std::make_unique<Rectangle>());✅ PImpl惯用法
// Widget.h class Widget { class Impl; std::unique_ptr<Impl> pImpl; }; // Widget.cpp class Widget::Impl { /* ... */ };❌ 不适合场景
需要共享所有权时
需要在多个地方同时引用同一个对象
三、shared_ptr——共享所有权
核心特性
引用计数共享所有权
最后一个shared_ptr销毁时释放资源
线程安全(引用计数操作原子化)
比unique_ptr重(额外维护控制块)
适用场景
✅ 多个对象共享同一资源
class Texture { // 大型纹理数据 }; std::shared_ptr<Texture> tex1 = std::make_shared<Texture>(); auto tex2 = tex1; // 两个对象共享同一纹理✅ 回调与异步操作
void asyncProcess(std::shared_ptr<Data> data) { std::thread([data]() { // 确保异步执行期间data存活 process(data); }).detach(); }✅ 缓存系统
class CacheManager { std::map<std::string, std::shared_ptr<ExpensiveObject>> cache; std::shared_ptr<ExpensiveObject> get(const std::string& key) { auto it = cache.find(key); if (it != cache.end()) return it->second; auto obj = std::make_shared<ExpensiveObject>(); cache[key] = obj; return obj; } };✅ 复杂数据结构中的共享节点
struct TreeNode { int value; std::shared_ptr<TreeNode> left, right; std::weak_ptr<TreeNode> parent; // 避免循环引用 };❌ 不适合场景
简单独占所有权(用unique_ptr更高效)
存在循环引用可能时(需配合weak_ptr)
对性能极度敏感的场景
四、weak_ptr——弱引用观察者
核心特性
不增加引用计数
不能直接访问对象,必须lock()提升为shared_ptr
解决shared_ptr循环引用问题
用于观察资源是否存活
适用场景
✅ 打破循环引用
class Child { std::shared_ptr<Parent> parent_; // 错误!循环引用 std::weak_ptr<Parent> parent_; // 正确 }; class Parent { std::vector<std::shared_ptr<Child>> children_; };✅ 缓存失效检测
class ExpensiveCache { std::map<int, std::weak_ptr<ExpensiveObj>> cache; std::shared_ptr<ExpensiveObj> get(int id) { auto it = cache.find(id); if (it != cache.end()) { if (auto obj = it->second.lock()) return obj; // 缓存命中且有效 else cache.erase(it); // 清理过期条目 } auto obj = std::make_shared<ExpensiveObj>(id); cache[id] = obj; return obj; } };✅ 观察者模式
class Observer { std::weak_ptr<Subject> subject_; public: void update() { if (auto sub = subject_.lock()) { // 安全地访问subject } } };✅ 临时访问非拥有资源
class ResourcePool { std::vector<std::weak_ptr<Resource>> resources_; std::shared_ptr<Resource> acquire() { for (auto& wp : resources_) { if (auto r = wp.lock()) { return r; // 复用已有资源 } } return nullptr; } };五、实战对比表
特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
所有权 | 独占 | 共享 | 无(观察) |
引用计数 | 无 | 有(原子操作) | 无 |
拷贝语义 | 禁止 | 允许(计数+1) | 允许 |
移动语义 | 允许 | 允许 | 允许 |
性能开销 | 极小 | 较大 | 中等 |
内存占用 | 同裸指针 | 约多8字节 | 约多4字节 |
线程安全 | 是 | 引用计数安全 | 同shared_ptr |
自定义删除器 | 支持 | 支持 | 不支持 |
六、最佳实践总结
选择原则
优先unique_ptr:除非明确需要共享所有权
使用make_shared/make_unique:异常安全且效率高
警惕循环引用:用weak_ptr打断
避免裸指针传递所有权:用unique_ptr::get()仅作临时访问
常见反模式
// ❌ 不要这样做 auto sp = std::make_shared<int>(42); std::weak_ptr<int> wp = sp; if (!wp.expired()) { // 竞态条件! auto sp2 = wp.lock(); // 可能已经失效 } // ✅ 正确做法 if (auto sp2 = wp.lock()) { // 安全使用sp2 }现代C++资源管理准则
不使用new/delete(除非实现底层库)
所有动态资源用智能指针包装
接口参数用智能指针明确所有权语义
返回对象用unique_ptr,接受共享用shared_ptr
七、进阶技巧
enable_shared_from_this
当需要在类内部获取自身的shared_ptr时:
class Node : public std::enable_shared_from_this<Node> { public: std::shared_ptr<Node> getShared() { return shared_from_this(); } };别名构造函数(C++17)
auto ptr = std::make_shared<Foo>(args); std::shared_ptr<Bar> alias(ptr, &ptr->bar); // 共享控制块,但指向成员数组支持(C++17)
auto arr = std::make_unique<int[]>(100); auto arr_sp = std::shared_ptr<int[]>(new int[100], std::default_delete<int[]>());掌握这三种智能指针的适用场景,能让你写出更安全、更高效的C++代码。记住:正确的所有权设计比任何优化都重要。
