C++:一文读懂智能指针

C++11 引入了 3 个智能指针类型:
当使用智能指针时,我们首先需要包含 memory头文件,这个头文件包含了 C++ 标准库中智能指针的定义。
1.std::unique_ptr<T> :独占资源所有权的指针。
2.std::shared_ptr<T> :共享资源所有权的指针。
3.std::weak_ptr<T> :共享资源的观察者,需要和 std::shared_ptr 一起使用,不影响资源的生命周期。
std::auto_ptr 已被废弃。

std::unique_ptr 的使用

#include <iostream>
#include <memory>int main() {// 创建一个 std::unique_ptr,指向一个动态分配的整数std::unique_ptr<int> ptr(new int(42));// 使用智能指针访问其所管理的对象std::cout << "值:" << *ptr << std::endl;// 不需要手动释放内存,当 std::unique_ptr 离开作用域时会自动释放内存return 0;
}

在这个示例中,std::unique_ptr ptr(new int(42)); 创建了一个 std::unique_ptr,并将其指向动态分配的整数。当 ptr 离开作用域时,它所管理的整数会自动被释放,无需手动调用 delete。

std::unique_ptr 的原理是基于资源获取即初始化(RAII)的概念。它在构造时接管了动态分配的内存,然后在析构时自动释放该内存。由于 std::unique_ptr 不能进行拷贝或赋值,因此保证了独占所有权的原则。

std::unique_ptr

简单说,当我们独占资源的所有权的时候,可以使用 std::unique_ptr 对资源进行管理——离开 unique_ptr 对象的作用域时,会自动释放资源。这是很基本的 RAII 思想。

std::unique_ptr 的使用比较简单,也是用得比较多的智能指针。这里直接看例子。

1.使用裸指针时,要记得释放内存。

{int* p = new int(100);// ...delete p;  // 要记得释放内存
}

1.使用 std::unique_ptr 自动管理内存。

{std::unique_ptr<int> uptr = std::make_unique<int>(200);//...// 离开 uptr 的作用域的时候自动释放内存
}

1.std::unique_ptr 是 move-only 的。

{std::unique_ptr<int> uptr = std::make_unique<int>(200);std::unique_ptr<int> uptr1 = uptr;  // 编译错误,std::unique_ptr<T> 是 move-only 的std::unique_ptr<int> uptr2 = std::move(uptr);assert(uptr == nullptr);
}

1.std::unique_ptr 可以指向一个数组。

{std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10);for (int i = 0; i < 10; i++) {uptr[i] = i * i;}   for (int i = 0; i < 10; i++) {std::cout << uptr[i] << std::endl;}   
}

1.自定义 deleter。

{struct FileCloser {void operator()(FILE* fp) const {if (fp != nullptr) {fclose(fp);}}   };  std::unique_ptr<FILE, FileCloser> uptr(fopen("test_file.txt", "w"));
}

1.使用 Lambda 的 deleter。

{std::unique_ptr<FILE, std::function<void(FILE*)>> uptr(fopen("test_file.txt", "w"), [](FILE* fp) {fclose(fp);});
}

std::shared_ptr 的使用

#include <iostream>
#include <memory>int main() {// 创建一个 std::shared_ptr,指向一个动态分配的整数std::shared_ptr<int> ptr1(new int(42));// 创建另一个 std::shared_ptr 指向同一块内存std::shared_ptr<int> ptr2 = ptr1;// 使用智能指针访问其所管理的对象std::cout << "ptr1 的值:" << *ptr1 << std::endl;std::cout << "ptr2 的值:" << *ptr2 << std::endl;// 不需要手动释放内存,当最后一个指向该内存的 std::shared_ptr 离开作用域时会自动释放内存return 0;
}

在这个示例中,std::shared_ptr ptr1(new int(42)); 创建了一个 std::shared_ptr,并将其指向动态分配的整数。然后 std::shared_ptr ptr2 = ptr1; 创建了另一个 std::shared_ptr 指向同一块内存。由于 std::shared_ptr 使用引用计数来管理内存,因此当最后一个指向该内存的 std::shared_ptr 离开作用域时,内存会被自动释放。

std::shared_ptr 的原理是基于引用计数的概念。每个 std::shared_ptr 都会维护一个引用计数,当有新的 std::shared_ptr 指向同一块内存时,引用计数会增加,当 std::shared_ptr 离开作用域时,引用计数会减少,当引用计数为 0 时,内存会被释放。这样可以确保在不再需要时释放内存,避免内存泄漏。

std::shared_ptr

std::shared_ptr 其实就是对资源做引用计数——当引用计数为 0 的时候,自动释放资源。

{std::shared_ptr<int> sptr = std::make_shared<int>(200);assert(sptr.use_count() == 1);  // 此时引用计数为 1{   std::shared_ptr<int> sptr1 = sptr;assert(sptr.get() == sptr1.get());assert(sptr.use_count() == 2);   // sptr 和 sptr1 共享资源,引用计数为 2}   assert(sptr.use_count() == 1);   // sptr1 已经释放
}
// use_count 为 0 时自动释放内存

和 unique_ptr 一样,shared_ptr 也可以指向数组和自定义 deleter。

{// C++20 才支持 std::make_shared<int[]>// std::shared_ptr<int[]> sptr = std::make_shared<int[]>(100);std::shared_ptr<int[]> sptr(new int[10]);for (int i = 0; i < 10; i++) {sptr[i] = i * i;}   for (int i = 0; i < 10; i++) {std::cout << sptr[i] << std::endl;}   
}{std::shared_ptr<FILE> sptr(fopen("test_file.txt", "w"), [](FILE* fp) {std::cout << "close " << fp << std::endl;fclose(fp);});
}

std::shared_ptr 的实现原理

一个 shared_ptr 对象的内存开销要比裸指针和无自定义 deleter 的 unique_ptr 对象略大。

  std::cout << sizeof(int*) << std::endl;  // 输出 8std::cout << sizeof(std::unique_ptr<int>) << std::endl;  // 输出 8std::cout << sizeof(std::unique_ptr<FILE, std::function<void(FILE*)>>)<< std::endl;  // 输出 40std::cout << sizeof(std::shared_ptr<int>) << std::endl;  // 输出 16std::shared_ptr<FILE> sptr(fopen("test_file.txt", "w"), [](FILE* fp) {std::cout << "close " << fp << std::endl;fclose(fp);}); std::cout << sizeof(sptr) << std::endl;  // 输出 16

无自定义 deleter 的 unique_ptr 只需要将裸指针用 RAII 的手法封装好就行,无需保存其它信息,所以它的开销和裸指针是一样的。如果有自定义 deleter,还需要保存 deleter 的信息。

shared_ptr 需要维护的信息有两部分:

指向共享资源的指针。
引用计数等共享资源的控制信息——实现上是维护一个指向控制信息的指针。
所以,shared_ptr 对象需要保存两个指针。shared_ptr 的 的 deleter 是保存在控制信息中,所以,是否有自定义 deleter 不影响 shared_ptr 对象的大小。

当我们创建一个 shared_ptr 时,其实现一般如下:

std::shared_ptr<T> sptr1(new T);

在这里插入图片描述
复制一个 shared_ptr :

std::shared_ptr<T> sptr2 = sptr1;

在这里插入图片描述
为什么控制信息和每个 shared_ptr 对象都需要保存指向共享资源的指针?可不可以去掉 shared_ptr 对象中指向共享资源的指针,以节省内存开销?

答案是:不能。 因为 shared_ptr 对象中的指针指向的对象不一定和控制块中的指针指向的对象一样。

来看一个例子。

struct Fruit {int juice;
};struct Vegetable {int fiber;
};struct Tomato : public Fruit, Vegetable {int sauce;
};// 由于继承的存在,shared_ptr 可能指向基类对象
std::shared_ptr<Tomato> tomato = std::make_shared<Tomato>();
std::shared_ptr<Fruit> fruit = tomato;
std::shared_ptr<Vegetable> vegetable = tomato;

在这里插入图片描述
另外,std::shared_ptr 支持 aliasing constructor。

template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

Aliasing constructor,简单说就是构造出来的 shared_ptr 对象和参数 r 指向同一个控制块(会影响 r 指向的资源的生命周期),但是指向共享资源的指针是参数 ptr。看下面这个例子。

using Vec = std::vector<int>;
std::shared_ptr<int> GetSPtr() {auto elts = {0, 1, 2, 3, 4};std::shared_ptr<Vec> pvec = std::make_shared<Vec>(elts);return std::shared_ptr<int>(pvec, &(*pvec)[2]);
}std::shared_ptr<int> sptr = GetSPtr();
for (int i = -2; i < 3; ++i) {printf("%d\n", sptr.get()[i]);
}

在这里插入图片描述
看上面的例子,使用 std::shared_ptr 时,会涉及两次内存分配:一次分配共享资源对象;一次分配控制块。C++ 标准库提供了 std::make_shared 函数来创建一个 shared_ptr 对象,只需要一次内存分配。
在这里插入图片描述

这种情况下,不用通过控制块中的指针,我们也能知道共享资源的位置——这个指针也可以省略掉。
在这里插入图片描述

std::weak_ptr

std::weak_ptr 要与 std::shared_ptr 一起使用。 一个 std::weak_ptr 对象看做是 std::shared_ptr 对象管理的资源的观察者,它不影响共享资源的生命周期:

1.如果需要使用 weak_ptr 正在观察的资源,可以将 weak_ptr 提升为 shared_ptr。
2.当 shared_ptr 管理的资源被释放时,weak_ptr 会自动变成 nullptr。

void Observe(std::weak_ptr<int> wptr) {if (auto sptr = wptr.lock()) {std::cout << "value: " << *sptr << std::endl;} else {std::cout << "wptr lock fail" << std::endl;}
}std::weak_ptr<int> wptr;
{auto sptr = std::make_shared<int>(111);wptr = sptr;Observe(wptr);  // sptr 指向的资源没被释放,wptr 可以成功提升为 shared_ptr
}
Observe(wptr);  // sptr 指向的资源已被释放,wptr 无法提升为 shared_ptr

在这里插入图片描述
当 shared_ptr 析构并释放共享资源的时候,只要 weak_ptr 对象还存在,控制块就会保留,weak_ptr 可以通过控制块观察到对象是否存活。
在这里插入图片描述

enable_shared_from_this

一个类的成员函数如何获得指向自身(this)的 shared_ptr? 看看下面这个例子有没有问题?

class Foo {public:std::shared_ptr<Foo> GetSPtr() {return std::shared_ptr<Foo>(this);}
};auto sptr1 = std::make_shared<Foo>();
assert(sptr1.use_count() == 1);
auto sptr2 = sptr1->GetSPtr();
assert(sptr1.use_count() == 1);
assert(sptr2.use_count() == 1);

在这里插入图片描述
成员函数获取 this 的 shared_ptr 的正确的做法是继承 std::enable_shared_from_this。

class Bar : public std::enable_shared_from_this<Bar> {public:std::shared_ptr<Bar> GetSPtr() {return shared_from_this();}
};auto sptr1 = std::make_shared<Bar>();
assert(sptr1.use_count() == 1);
auto sptr2 = sptr1->GetSPtr();
assert(sptr1.use_count() == 2);
assert(sptr2.use_count() == 2);

一般情况下,继承了 std::enable_shared_from_this 的子类,成员变量中增加了一个指向 this 的 weak_ptr。这个 weak_ptr 在第一次创建 shared_ptr 的时候会被初始化,指向 this。

似乎继承了 std::enable_shared_from_this 的类都被强制必须通过 shared_ptr 进行管理。

auto b = new Bar;
auto sptr = b->shared_from_this();

小结

智能指针,本质上是对资源所有权和生命周期管理的抽象:

1.当资源是被独占时,使用 std::unique_ptr 对资源进行管理。
2.当资源会被共享时,使用 std::shared_ptr 对资源进行管理。
3.使用 std::weak_ptr 作为 std::shared_ptr 管理对象的观察者。
4.通过继承 std::enable_shared_from_this 来获取 this 的 std::shared_ptr 对象。

参考资料
1.智能指针

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/162638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构初阶(4)】栈的基本操作实现

文章目录 Ⅰ 概念及结构1. 栈的概念2. 栈的操作 Ⅱ 基本操作实现1. 栈的定义2. 初始化栈3. 元素入栈4. 元素出栈5. 获取栈顶元素6. 获取栈中有效元素个数7. 判断栈空8. 销毁栈 Ⅰ 概念及结构 1. 栈的概念 栈&#xff1a;栈是一种特殊的线性表&#xff0c;其只允许在固定的一端…

MATLAB实现灰色预测

久违了&#xff0c;前段时间由于学习压力大&#xff0c;就没怎么更新MATLAB相关的内容&#xff0c;今天实在学不进去了&#xff0c;换个内容更新一下~ 本贴介绍灰色预测模型&#xff0c;这也是数学建模竞赛常见算法中的一员&#xff0c;和许多预测模型一样——底层原理是根据已…

监控员工上网有什么软件丨三款好用的员工上网管理软件推荐

监控员工上网行为是企业管理中不可或缺的一部分&#xff0c;因此&#xff0c;选择一款好的监控员工上网的软件至关重要。目前市场上存在多种监控员工上网的软件&#xff0c;它们具有各种特点和功能&#xff0c;但企业需要仔细评估和选择。 一、域之盾软件 这是一款优秀的监控员…

利用GenericMenu创建上下文菜单或下拉菜单

使用GenericMenu 创建自定义上下文菜单和下拉菜单丰富自己的编辑器功能。 GenericMenu 介绍 变量 allowDuplicateNames 允许菜单具有多个同名的菜单项。 公共函数 AddDisabledItem 向菜单添加已禁用的项。 AddItem 向菜单添加一个项。 AddSeparator 向菜单添加一个分隔符项…

如何在AppLink配置金蝶云星空预算使用单流程

上一篇有提到金蝶云星空如何通过AppLink平台配置销售订单操作&#xff0c;这次来演示下如何“保存预算使用单”、“调拨单定时自动审核”以及“预算使用单反审核后删除”操作。 根据请求数据保存预算使用单 当webhook接收到数据时触发流程 步骤1&#xff1a;根据webhook的请…

OpenGL YUV 和 RGB 图像相互转换出现的偏色问题怎么解决?

未经作者(微信ID:Byte-Flow)允许,禁止转载 文章首发于公众号:字节流动 早上知识星球里的一位同学,遇到 yuv2rgb 偏色问题,这个问题比较典型,今天展开说一下。 省流版 首先 yuv2rgb 和 rgb2yuv 之间的转换要基于相同的标准,转换使用不同的标准肯定会引起偏色,常见的…

短视频矩阵系统源码搭建部署分享

一、 短视频矩阵系统源码搭建部署分享 目录 一、 短视频矩阵系统源码搭建部署分享 二、短视频矩阵系统搭建功能设计 三、 抖音矩阵号矩阵系统功能设计原则 四、 短视频矩阵开发部分源码展示 很高兴能够帮助您&#xff0c;以下是短视频矩阵系统源码搭建部署分享&#xff1a…

怎么快速卸载office365

怎么快速卸载office365 根据官网提供的两种解决方案即点即用或MSIMicrosoft Store 根据官网提供的两种解决方案 官网地址&#xff1a;https://support.microsoft.com/zh-cn/office/%E4%BB%8E-pc-%E5%8D%B8%E8%BD%BD-office-9dd49b83-264a-477a-8fcc-2fdf5dbf61d8#OfficeVersio…

智能优化算法 | Matlab实现金豺优化算法(GJO)(内含完整源码)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 智能优化算法 | Matlab实现金豺优化算法(GJO)(内含完整源码) 源码设计 %%clear clc close SearchAgents_no=30; % Number of search agents Max_iteration=1000

PHP 正则式 全能匹配URL(UBB)

PHP 正则式 全能匹配URL&#xff08;UBB&#xff09; 语言&#xff1a;PHP 注明&#xff1a;正则式 无语言限制&#xff08;js、PHP、JSP、ASP、VB、.net、C#...&#xff09;一切皆可。 简介&#xff1a;PHP UBB 正则式 全能匹配URL 自动加超级链接。网上找了很多都不匹配或…

数字化转型过程中面临最大的问题是什么?如何借助数字化工具实现快速转型?

在科技快速发展的时代&#xff0c;数字化转型已经成为企业的重要战略。当企业努力适应数字化时代并取得成功时&#xff0c;他们可能会面临各种必须有效应对的挑战。   数字化转型不仅仅是将新技术应用到企业的运营中&#xff0c;还需要对企业的运营方式、与客户的互动方式和价…

数字人直播系统开发要注意的陷阱

数字人做为元宇宙的底层基座&#xff0c;BAT都在跑步进场&#xff0c;目前具有前瞻性的公司都在布局数字人产业。数字人可以应用于很多业务场景&#xff0c;对今年来说&#xff0c;无疑数字人直播系统是最火的。像去年数字人直播SAAS系统定制开发的话没有个百把万是下不来的。但…

求臻医学六周年,我们的故事值得被记录

6载光阴求臻医学持续以科技创新推动产业升级在肿瘤精准诊疗领域持续深耕致力于为肿瘤患者生命续航每一位求臻人都是我们前进的力量也是我们不断创新的源泉我们不拘泥于一种声音以多元化的视角探索前行

app小程序定制的重点|软件定制开发|网站搭建

app小程序定制的重点|软件定制开发|网站搭建 App小程序定制开发是近年来快速发展的一项技术服务&#xff0c;随着移动互联网的普及和用户需求的不断升级&#xff0c;越来越多的企业和个人开始关注和需求定制化的小程序开发。那么&#xff0c;对于app小程序定制开发来说&#xf…

【微信小程序】绘制二维码实现及解决canvas层级问题最佳实践

前言 很久没写微信小程序的需求了&#xff0c;今天来活儿要做个二维码扫码相关的需求&#xff0c;本来以为是洒洒水的事情&#xff0c;谁知道也折磨了大半天&#xff0c;今天特此记录一下~ 需求&#xff1a;点击按钮&#xff0c;弹出二维码&#xff0c;二维码内容固定为test …

Dubbo框架

1&#xff1a;简介 Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架 Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题 这是Dubbo官网的介绍&#xff0c;下面是我对这dubbo的理解 首先介绍下什么是RPC&#xff1a; 常…

为什么说品牌低价不是一件好事

消费者货比三价为的是买到低价质优的产品&#xff0c;而网络电商平台的公开&#xff0c;也促进了消费者及品牌进行比价&#xff0c;那品牌低价一定就是好事吗&#xff0c;一定会拉高品牌销量吗。其实是不一定的&#xff0c;低价意味着成本的降低&#xff0c;也可能滋生很多产品…

【精选】改进的YOLOv5:红外遥感图像微型目标的高效识别系统

1.研究背景与意义 随着科技的不断发展&#xff0c;红外遥感技术在军事、安防、环境监测等领域中得到了广泛应用。红外遥感图像具有独特的优势&#xff0c;可以在夜间或恶劣天气条件下获取目标信息&#xff0c;因此在小目标检测方面具有重要的应用价值。然而&#xff0c;由于红…

专注短视频账号矩阵系统源头开发---saas工具

专注短视频账号矩阵系统源头开发---saas营销化工具&#xff0c;目前我们作为一家纯技术开发团队目前已经专注打磨开发这套系统企业版/线下版两个版本的saas营销拓客工具已经3年了&#xff0c;本套系统逻辑主要是从ai智能批量剪辑、账号矩阵全托管发布、私信触单收录、文案ai智能…

微服务实战系列之签名Sign

前言 昨日恰逢“小雪”节气&#xff0c;今日寒风如约而至。清晨的马路上&#xff0c;除了洋洋洒洒的落叶&#xff0c;就是熙熙攘攘的上班族。眼看着&#xff0c;暖冬愈明显了&#xff0c;叶子来不及泛黄就告别了树。变化总是在不经意中发生&#xff0c;容不得半刻糊涂。 上集博…