为什么需要智能指针?
在传统的 C++ 开发中,我们习惯用 new 和 delete 手动管理堆内存。但这很容易出问题。
void riskyFunction() {
MyClass* ptr = new MyClass();
// ... 业务逻辑 ...
if (some_condition) {
throw std::runtime_error("Oops!");
// 如果这里抛出异常,下面的 delete ptr 不会被执行!
}
delete ptr;
}
如果在 delete 之前因为条件判断、异常或者疏忽提前返回,指针指向的内存就永远无法释放,导致内存泄漏。这就是引入智能指针的根本原因。
核心思想:RAII
资源获取即初始化(Resource Acquisition Is Initialization)。原理其实很简单却极其强大:
- 构造函数中获取资源(分配内存、打开文件、加锁)。
- 析构函数中释放资源(释放内存、关闭文件、解锁)。
C++ 保证当一个对象离开作用域时(无论是正常退出还是因异常),它的析构函数一定会被自动调用。智能指针正是将 RAII 思想应用于动态内存管理的完美范例,它将裸指针包装成类对象,并在析构时自动执行 delete。
主要智能指针类型
C++11 引入了三种主要的智能指针,位于 <memory> 头文件中。
1. unique_ptr:独占所有权
这是最基础的智能指针,特性非常明确:
- 独占所有权:一个资源只能被一个
unique_ptr拥有。 - 禁止拷贝:不能拷贝构造,也不能拷贝赋值。
- 支持移动:可以通过
std::move转移所有权。 - 零开销:与裸指针相比几乎没有性能损失。
创建时推荐使用工厂函数 make_unique(C++14 引入):
#include <memory>
#include <iostream>
int main() {
// 方式 1:直接构造(不推荐)
std::unique_ptr<int> p1(new int(42));
// 方式 2:工厂函数(推荐)
auto p2 = std::make_unique<int>(100);
auto p3 = std::make_unique<std::string>("Hello");
std::cout << *p2 << std::endl;
return 0;
}
2. shared_ptr:共享所有权
当多个地方都需要访问同一个对象时,shared_ptr 是更好的选择。
- 共享所有权:多个
shared_ptr可以共同拥有同一个对象。 - 引用计数:内部维护一个计数器,记录有多少个指针指向该对象。
- 自动释放:当最后一个指向对象的
shared_ptr销毁或重置时,对象才会被删除。
注意事项:
- 优先使用
make_shared:它是一次内存分配(对象 + 控制块),效率比直接new更高。 - 不要混用原始指针:不要用同一个裸指针初始化多个
shared_ptr,这会导致重复释放。 - 小心循环引用:两个对象互相持有
shared_ptr会导致引用计数永远不为 0,此时应使用weak_ptr。
// 正确做法
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
// 错误做法
MyClass* raw = new MyClass();
std::shared_ptr<MyClass> p1(raw);
std::shared_ptr<MyClass> p2(raw); // 危险!两个独立计数,都会 delete raw
3. weak_ptr:解决循环引用
weak_ptr 同样也是 C++11 的智能指针,它的出现主要是为了解决 shared_ptr 的循环引用问题。
- 它不拥有对象,不会增加引用计数。
- 它可以像指针一样使用,但需要先通过
lock()转换为shared_ptr才能安全访问。 - 常用于打破循环引用或作为临时观察者。
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用
};
工厂函数:make_unique / make_shared
make_unique<T>(args...):创建unique_ptr。make_shared<T>(args...):创建shared_ptr。
它们不仅更安全(防止异常导致的内存泄漏),对于 shared_ptr 还能优化内存分配(一次分配对象和控制块)。
总结
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 | 共享 | 不拥有 |
| 拷贝 | 禁止 | 允许 | 允许 |
| 移动 | 允许 | 允许 | 允许 |
| 性能 | 零开销 | 有开销 | 有开销 |
| 适用场景 | 单一明确所有者 | 多个不确定的所有者 | 打破循环引用、缓存 |
最佳实践建议:
- 首选
unique_ptr:除非你需要共享所有权,否则默认使用它,最安全高效。 - 使用工厂函数:始终优先使用
make_unique和make_shared。 - 避免裸指针:将所有的
new和delete替换为智能指针。 - 警惕循环引用:涉及双向关系时,记得检查是否需要
weak_ptr。

