【C++】智能指针

目录

一、先搞懂:为什么需要智能指针?

二、三种智能指针,用一句话记住

三、unique_ptr:独生子女

什么时候自动退房?

为什么默认用它?

四、shared_ptr:合租房

场景

关键点

怎么创建?

五、weak_ptr:旁观者

场景

那它有什么用?

六、最经典的问题:循环引用

场景

解决:让一边放手

七、一张图记住全部

[八、实战 ------ 代码](#八、实战 —— 代码)

九、总结


一、先搞懂:为什么需要智能指针?

裸指针问题

普通指针就像一把钥匙:

复制代码
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_ptr b = 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() 才能用

选择口诀:

  1. 默认用 unique_ptr。

  2. 确实要共享,才用 shared_ptr。

  3. 只是观察 / 打破循环,用 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()确认还活着。专门解决循环引用

相关推荐
青峰讲运维26 分钟前
OpenStack Heat 编排:用模板一键交付云业务,告别手动点页面
开发语言·php·openstack
MyFreeIT1 小时前
Python Windows Manual
开发语言·python
kiracrimson1 小时前
STL:List的使用和模拟实现
开发语言·c++·list
二十七日秋1 小时前
算法刷题笔记「Day 1|四道 OJ 入门题里的深坑」
c语言·汇编·数据结构·c++·笔记·python·算法
流年如夢1 小时前
继承与多态_继承篇
c++·继承·面向对象·c++进阶
北冥you鱼2 小时前
Go 语言 GORM 使用 Preload:从关联查询到嵌套预加载的完整实践
开发语言·jvm·golang
沫璃染墨2 小时前
从零入门计算机网络系列(十):HTTP协议进阶——从资源路径解析到状态码与请求头详解
linux·运维·服务器·网络·c++·http
dingzd952 小时前
TikTok账号登录态失效的Cookie字段排查路径
开发语言·php·跨境电商·营销策略
曹牧2 小时前
C#:The operation has timed out
开发语言·c#
xiaoye-duck2 小时前
《C++ 工具库实战》高性能日志 spdlog:从基础使用到工程化封装
c++·第三方库