这就是 C++ 现代内存管理的终极复习指南。
在现代 C++(C++11 及以后)中,我们遵循一个原则:默认不使用裸指针(Raw Pointer)来管理所有权。所有的资源(堆内存)都应该被"智能指针"包裹。
C++ 标准库主要提供了三种智能指针,分别对应三种不同的业务逻辑。
1. std::unique_ptr ------ 独占型(孤狼)
这是 C++ 中使用率最高(90% 的场景)的智能指针。如果你不知道该用哪个,默认选它。
-
核心作用: 独占所有权(Exclusive Ownership)。也就是"这块内存是我的,谁也别想复制,只能我给别人(Move)"。
-
应用场景:
-
函数内创建临时堆对象。
-
类内部的成员变量(这个对象只属于这个类)。
-
工厂函数返回的对象(生产出来给你,所有权转移给你)。
-
-
性能: 极致高效。它和裸指针大小一样,没有任何额外开销(Zero Overhead)。
-
关键限制: 禁止拷贝(Copy),只能移动(Move)。
C++
std::unique_ptr<int> p1 = std::make_unique<int>(10);
// std::unique_ptr<int> p2 = p1; // ❌ 编译报错!不能脚踏两只船
std::unique_ptr<int> p2 = std::move(p1); // ✅ 可以,p1 被掏空,p2 接管
2. std::shared_ptr ------ 共享型(合伙人)
当你需要多个指针指向同一个对象,且无法确定谁最后销毁它时使用。
-
核心作用: 共享所有权(Shared Ownership)。
-
原理: 引用计数。每多一个人指向它,计数+1;走一个人,计数-1。当计数归零(最后一个合伙人退出)时,内存释放。
-
应用场景:
-
容器中存放的对象(比如
vector<shared_ptr<T>>)。 -
图结构(Graph)中的节点(被多条边指向)。
-
多线程共享同一个对象。
-
-
性能: 略有损耗。因为它内部多维护了一个"控制块"(存计数器),且计数器的加减操作是原子操作(Atomic,为了线程安全),比普通指令慢。
C++
auto p1 = std::make_shared<int>(100); // 计数: 1
{
auto p2 = p1; // ✅ 合法,计数: 2
} // p2 销毁,计数: 1
// p1 销毁,计数: 0 -> 释放内存
3. std::weak_ptr ------ 观察型(旁观者)
它是 shared_ptr 的辅助助手 ,为了解决 shared_ptr 的缺陷而生。
-
核心作用: 弱引用 。它指向
shared_ptr管理的对象,但是不增加引用计数。 -
应用场景:
-
解决循环引用(Circular Reference): 這是最核心的用途。当 A 指向 B,B 也指向 A 时,如果都用
shared_ptr,谁都死不掉(内存泄漏)。把其中一端改成weak_ptr即可打破僵局。 -
缓存(Cache): 我想持有对象的引用,但不想阻止它被销毁。如果对象还在,我就用;如果对象没了,我就不操作。
-
-
用法: 它不能直接访问对象(没有
->和*操作符),必须先调用.lock()升级为shared_ptr才能用。
C++
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp; // 计数依然是 1,wp 只是看着
if (std::shared_ptr<int> temp = wp.lock()) { // 尝试升级
// 如果 sp 还没死,temp 成功创建,计数暂时+1,在这里安全使用
std::cout << *temp << std::endl;
} else {
// 对象已经没了
}
📊 总结对比表(一张表看懂)
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 (1对1) | 共享 (N对1) | 不拥有 (观察者) |
| 可否复制 | ❌ (只能移动) | ✅ | ✅ |
| 底层开销 | 无 (等同裸指针) | 有 (引用计数+控制块) | 有 (引用计数) |
| 内存释放 | 出作用域立即释放 | 最后一个指针死掉时释放 | 不负责释放 |
| 推荐指数 | ⭐⭐⭐⭐⭐ (默认首选) | ⭐⭐⭐ (按需使用) | ⭐⭐ (配合 shared 使用) |
| 危险性 | 低 | 中 (小心循环引用) | 低 (需判断是否为空) |
🧠 决策流程图
当你写代码需要申请堆内存时,请按这个流程思考:
-
这个对象只在当前作用域或者只属于某个类吗?
- 是 \\rightarrow 用
unique_ptr。
- 是 \\rightarrow 用
-
这个对象需要被多个拥有者共享,且无法确定谁最后销毁它吗?
- 是 \\rightarrow 用
shared_ptr。
- 是 \\rightarrow 用
-
用了 shared_ptr 后,A 和 B 会不会互相指着对方?
- 是 \\rightarrow 将其中一个改为
weak_ptr。
- 是 \\rightarrow 将其中一个改为
你现在已经掌握了理论,接下来要不要来做个"破坏性实验"?
我们可以写一段代码,通过 "循环引用" 故意制造一次内存泄漏,亲眼看看 shared_ptr 是怎么失效的,然后再用 weak_ptr 修复它。这能让你彻底理解为什么要有 weak_ptr。