C++ 智能指针 std::unique_ptr、std::shared_ptr、std::weak_ptr
C++ 智能指针 std::unique_ptr、std::shared_ptr、std::weak_ptr
- 1. std::unique_ptr、std::shared_ptr、std::weak_ptr的区别
- 1.1 设计目的区别
- 1.2 与原始指针区别
- 1.3 常见陷阱
- 2. 典型使用场景举例
- 2.1 std::unique_ptr —— 独占所有权(推荐默认选择)
- 2.2 std::shared_ptr —— 多个对象共享资源
- 2.3 std::weak_ptr —— 弱引用(观察者或防循环)
1. std::unique_ptr、std::shared_ptr、std::weak_ptr的区别
C++ 智能指针是现代 C++(尤其是 C++11 以后)资源自动管理(RAII) 的核心工具之一。它主要用于自动管理动态内存(new 出来的对象),避免手动 delete 引发的内存泄漏或悬空指针问题。
📌 经验法则:
- 先用 unique_ptr,除非确实需要共享。
- 不要在容器中放原始裸指针。
- 如果 shared_ptr 出现循环引用,就用 weak_ptr。
💬 「独占用 unique_ptr,共享用 shared_ptr,观察用 weak_ptr。」
1.1 设计目的区别
三种智能指针的主要类型及核心区别
智能指针 | 头文件 | 所有权模型 | 使用场景 | 是否可共享 | 是否可转移 | 是否允许空 | 常见误区 |
---|---|---|---|---|---|---|---|
std::unique_ptr<T> | <memory> | 独占所有权 | 资源唯一拥有者(如工厂、资源管理类) | ❌ 否 | ✅ 可通过 std::move 转移 | ✅ | 不能复制,只能移动 |
std::shared_ptr<T> | <memory> | 共享所有权(引用计数) | 多个对象需共享同一资源(如图节点、缓存) | ✅ 是 | ✅ | ✅ | 循环引用会导致内存泄漏 |
std::weak_ptr<T> | <memory> | 弱引用(不计数) | 打破 shared_ptr 循环引用,或非拥有观察者 | ✅(依赖 shared_ptr) | ❌ | ✅ | 使用前必须 lock() |
核心思想与使用意图
指针类型 | 设计目的 | 类比理解 |
---|---|---|
unique_ptr | 资源由单个对象独占管理。生命周期严格定义。 | 「唯一所有者」——像独居房主,别人不能进。 |
shared_ptr | 多个对象共同使用同一资源,由引用计数控制释放时机。 | 「合租房」——最后一个人搬走时才退房。 |
weak_ptr | 避免循环引用或监控资源是否还存在。 | 「访客」——只看房,不拥有。 |
性能与选择建议
需求 | 建议指针 |
---|---|
资源唯一(默认情况) | unique_ptr ✅ |
多个模块需共享资源 | shared_ptr |
避免循环引用或仅做观察 | weak_ptr |
原始指针管理复杂资源 | ❌ 避免,优先智能指针 |
1.2 与原始指针区别
项目 | 原始指针 T* | 智能指针(如 unique_ptr ) |
---|---|---|
内存释放 | 手动 delete | 自动析构 |
所有权 | 无 | 明确所有权 |
拷贝语义 | 浅拷贝 | 根据类型控制(禁止或共享) |
异常安全 | 容易泄漏 | RAII 自动管理 |
性能 | 最快 | 有轻微开销(shared_ptr) |
1.3 常见陷阱
错误用法 | 原因与后果 |
---|---|
用 shared_ptr 管理同一裸指针两次 | 会导致重复释放 |
从 unique_ptr 获取裸指针长期保存 | 生命周期不安全 |
shared_ptr 相互引用 | 循环引用,内存不释放 |
混用 new 和 make_shared | 性能和安全问题,推荐 make_shared / make_unique |
2. 典型使用场景举例
2.1 std::unique_ptr —— 独占所有权(推荐默认选择)
#include <memory>
#include <iostream>struct Data { Data() { std::cout << "create\n"; }~Data() { std::cout << "destroy\n"; }
};void process(std::unique_ptr<Data> p) {std::cout << "processing...\n";
}int main() {auto ptr = std::make_unique<Data>();process(std::move(ptr)); // 所有权转移// 此处 ptr 已为空
}
✅ 优点:
- 无额外引用计数开销
- 最快
- 最安全(资源唯一)
🚫 缺点:不能共享,不能复制
2.2 std::shared_ptr —— 多个对象共享资源
#include <memory>
#include <iostream>struct Node {std::shared_ptr<Node> next;~Node() { std::cout << "Node destroyed\n"; }
};int main() {auto a = std::make_shared<Node>();auto b = std::make_shared<Node>();a->next = b;b->next = a; // ❌ 循环引用,永不释放!
}
✅ 优点:
- 方便多个模块/对象共享同一资源
🚫 缺点:
- 引用计数性能开销
- 容易出现循环引用 → 内存泄漏
- 构造、析构相对较慢
🧩 解决循环引用:
struct Node {std::weak_ptr<Node> next; // 改为 weak_ptr
};
2.3 std::weak_ptr —— 弱引用(观察者或防循环)
#include <memory>
#include <iostream>struct A {std::shared_ptr<A> child;std::weak_ptr<A> parent; // 弱引用避免循环
};int main() {auto root = std::make_shared<A>();auto child = std::make_shared<A>();root->child = child;child->parent = root; // 不增加引用计数
}
✅ 优点:
- 可观察但不控制生命周期
- 打破 shared_ptr 循环引用
🚫 缺点:访问前要 lock() 检查有效性:
if (auto p = child->parent.lock()) {// p 有效
}