目录
[八、实战 ------ 代码](#八、实战 —— 代码)
一、先搞懂:为什么需要智能指针?
裸指针问题
普通指针就像一把钥匙:
Foo* p = new Foo(); // 拿钥匙,开了一间房
// ... 用着用着忘了归还
// 程序结束,房间还占着 → 内存泄漏
或者:
Foo* p = new Foo();
delete p; // 退房
delete p; // 又退一次 → 程序崩溃(double free)
或者中途出异常:
Foo* p = new Foo();
doSomething(); // 这里抛异常了
delete p; // 永远执行不到 → 泄漏
核心问题:钥匙在你手里,但"什么时候还"全靠你记得,人会忘,异常会跳
智能指针是什么?
智能指针就是带自动退房功能的房卡
你把房卡交给一个"管家对象",管家在自己离开作用域(生命周期结束)时自动退房,不管你是正常走、还是中途出异常,它都会退。
这就是 RAII:资源绑定到对象生命周期上,对象死,资源自动释放
二、三种智能指针,用一句话记住
| 类型 | 一句话 |
|---|---|
unique_ptr |
独生子女:这东西只能一个人拥有,别人要就得"过户" |
shared_ptr |
合租房:多个人一起拥有,最后一个人走才退房 |
weak_ptr |
旁观者:我只看看,不参与拥有,房客走光了我也不拦着 |
下面开始介绍!
三、unique_ptr:独生子女
场景
你有一本书,只能属于一个人 。你想给朋友,必须彻底放手,不能你俩都拿着。
std::unique_ptr<Foo> a(new Foo()); // a 拥有这本书
// std::unique_ptr<Foo> b = a; // 错!不能两个人同时拥有
std::unique_ptr<Foo> b = std::move(a); // 对!把所有权"过户"给 b,a 变空
std::move 就是过户。过户后:
-
b拥有书。 -
a变成空(nullptr),不能再用了
什么时候自动退房?
void func() {
std::unique_ptr<Foo> p(new Foo());
// ...
} // 函数结束,p 析构 → 自动 delete,你什么都不用写
就这么简单:出了大括号,自动释放。
为什么默认用它?
因为它零额外开销 ,和裸指针几乎一样快,但不用你手动 delete。没有共享需求时,首选它
四、shared_ptr:合租房
场景
一个房子,多个人合租。墙上有个计数器:
-
每来一个室友,计数 +1。
-
每走一个室友,计数 -1。
-
计数归 0 时,房子才退租(对象才析构)。
std::shared_ptr
a = std::make_shared (); // 计数 = 1
{
std::shared_ptrb = a; // b 也住进来,计数 = 2
std::cout << a.use_count(); // 打印 2
} // b 走了,计数 = 1
// a 还活着,房子还在
// a 析构时,计数归 0,房子退租
use_count() 就是当前有几个人住。
关键点
-
拷贝一份
shared_ptr→ 计数 +1。 -
销毁一份 → 计数 -1。
-
最后一个销毁的人负责退房。
怎么创建?
auto p = std::make_shared<Foo>(); // 推荐,一次分配,效率高
别这么写(有坑):
Foo* raw = new Foo();
std::shared_ptr<Foo> p1(raw);
std::shared_ptr<Foo> p2(raw); // 错!两个独立计数器,最后 double delete
记住:同一个裸指针,只能交给一个 shared_ptr 去管
五、weak_ptr:旁观者
场景
你在咖啡店看窗外的人。你看着他们,但你不拥有他们,他们走了你也不负责。
weak_ptr 就是这种"看着但不拥有"的指针。
它不增加计数,所以它不会阻止对象被销毁。
那它有什么用?
它不能直接访问对象,得先"问一句:人还在吗?"
std::weak_ptr<Foo> w = a; // w 只是观察 a,计数不变
if (auto locked = w.lock()) {
// 人还在,locked 是个临时的 shared_ptr,可以安全用
locked->doSomething();
} else {
// 人已经走了
}
lock() 就是确认还活着,并且临时拿到一张房卡。安全
六、最经典的问题:循环引用
这是 weak_ptr 存在的最主要原因,一定要看懂。
场景
两个人,A 拿着 B 的房卡,B 拿着 A 的房卡。
struct Node {
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // a 抓住 b
b->prev = a; // b 抓住 a
现在:
-
外面
a、b这两个变量销毁了。 -
但
a对象还被b->prev抓着,b对象还被a->next抓着。 -
计数永远不归 0,谁也不放手,房子永远退不了 → 内存泄漏。
解决:让一边放手
把其中一个改成 weak_ptr:
bash
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用,不抓人
};
现在 b->prev = a 只是"看着" a,不增加计数。外面的 a 销毁时,a 对象就真的被释放了,链子断了,b 也能释放。
记住一句话:双向关系里,一边用 shared_ptr,另一边用 weak_ptr
七、一张图记住全部
bash
unique_ptr → 独占,不能拷贝,只能 move
shared_ptr → 共享,有计数,归 0 才释放
weak_ptr → 旁观,不计数,lock() 才能用
选择口诀:
-
默认用
unique_ptr。 -
确实要共享,才用
shared_ptr。 -
只是观察 / 打破循环,用
weak_ptr
八、实战 ------ 代码
cpp
#include <iostream>
#include <memory>
struct Foo {
Foo() {
std::cout << "开了房间\n";
}
~Foo() {
std::cout << "退了房间\n";
}
};
int main() {
std::cout << "场景1:---\n";
// 1、unique_ptr
{
std::unique_ptr<Foo> up(new Foo());
}
//打印出,退出房间
std::cout << "场景2:---\n";
//2、shared_ptr
{
std::shared_ptr<Foo> sp1 = std::make_shared<Foo>();
std::cout << "计数:" << sp1.use_count() << std::endl;
{
std::shared_ptr<Foo> sp2 = sp1;
std::cout << "计数:" << sp2.use_count() << std::endl;
}
std::cout << "计数:" << sp1.use_count() << std::endl;
}
//打印出,退出房间
std::cout << "场景3:---\n";
//3、weak_ptr
{
std::weak_ptr<Foo> wp;
{
std::shared_ptr<Foo> sp = std::make_shared<Foo>();
wp = sp;
std::cout << "计数:" << wp.use_count() << std::endl;
}//sp 没了房间也退了
if (wp.expired()) {
std::cout << "房间已经不存在了\n";
}
}
return 0;
}

九、总结
unique_ptr:一个人拥有,要给别人就需要std::move,出了大括号自动释放
shared_ptr:多人合租,计数归0才释放,用make_shared创建
weak_ptr:只观察不拥有,用lock()确认还活着。专门解决循环引用