C/C++语言基础--C++智能指针(unique_ptr、shared_ptr、week_ptr)

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • 在C、C++语言中,最经典的特性就是指针,他和内存相关,但是我们常常申请内存后忘记释放而导致内存泄漏,C++提供了智能指针去解决这个内存泄漏问题
  • C语言后面也会继续更新知识点,如内联汇编;
  • 欢迎收藏 + 关注,本人将会持续更新。

文章目录

  • 智能指针
    • 简介
    • 为什么要使用智能指针
    • unique_ptr(类模板)
      • 构造对象
      • 删除器
      • 指针使用
      • 获取/释放
        • get
        • release
        • reset
    • shared_ptr
      • 常见错误
      • shared_ptr 内部指向两个位置
      • 构造对象
      • 删除器
      • 指针使用
      • 获取/释放
        • use_count(是一个函数)
        • unique
        • get
        • reset
    • weak_ptr
      • 简介
      • 解决循环引用
      • weak_ptr函数
    • 我们需要使用enable_shared_from_this?
    • 什么时候用enable_shared_ptr?

智能指针

简介

智能指针是一个模板类,封装了裸指针,可以对指针进行安全的操作。

  • 使用RAII特点,将对象生命周期使用栈来管理
  • 智能指针区分了所有权,因此使用责任更为清晰
  • 智能指针大量使用操作符重载和函数内联特点,调用成本和裸指针无差别

为什么要使用智能指针

可以方便我们使用指针的时候,不用担心内存释放的问题

unique_ptr(类模板)

👁 翻译:独有指针

☑️ 头文件:

🎪 特点:

  • 两个unique_ptr 不能指向一个对象,不能进行复制操作只能进行移动操作,📧 也就是说一个对象申请的内存如果用这个unique_ptr智能指针去管理,只能用一个unique_ptr去管理,不能用同时多个unique_ptr管理。
  • 不支持: 算术运算,不能通过unique_ptr 操作指针的++、--;
  • 复制:只允许移动,不允许赋值;
  • 作用域: 离开作用域自动释放内存。

构造对象

1、可以使用unique_ptr提供的构造函数构造对象,可以构造对象,也可以构造数组!

  • 自己先申请一个内存,然后交接unique_ptr管理
std::unique_ptr<Test> p1(new Test);
std::unique_ptr<Test[]> p2(new Test[5]);   //注意一下数组的申请

还可以这样:

auto p3 = std::make_unique<Test>();
auto p4 = std::make_unique<Test[]>(5);     //注意Text[]   一定得声明数组

2、使用make_unique构造,然后使用auto自动类型推导,就很方便了。

  • make_unique 是将类型告诉他,它会自动申请一个内存。
unique_ptr<Test> up = p1;				//尝试引用已删除的函数
unique_ptr<Test> up1 = std::move(p1);	//可以移动

值得注意的是,unique_ptr禁用了拷贝和赋值操作,只能进行移动。

删除器

在某些时候,默认的释放操作不能满足咱们的需要,这个时候就需要自定义删除器。(在构造智能指针的第二个参数指定他的释放类型)

// 可以用:全局函数、lambda、函数包装器、仿函数作为删除器,如下:
void del_global(Test* ptr){delete ptr;}					//全局函数
auto del_lambda = [](Test* ptr) {delete ptr; };			//lambda
std::function<void(Test*)> del_function = del_lambda;	//函数包装器
struct Del_Object										//仿函数
{void operator()(Test* ptr){delete ptr;}
};int main()
{std::unique_ptr<Test, decltype(del_global)*>	p2(new Test, del_global);// void(*)(Text*)    typeid(Del_Object).name()std::unique_ptr<Test, decltype(del_lambda)>		p3(new Test, del_lambda);std::unique_ptr<Test, decltype(del_function)>	p4(new Test, del_function);std::unique_ptr<Test, decltype(Del_Object())>	p5(new Test, Del_Object());//获取删除器  get_deleter方法auto delfun = p1.get_deleter();return 0;
}

指针使用

🏗 重载: 类中只用了大量的重载运算符,故和平常指针使用起来基本没差别。

判断

因为重载了operator bool()函数,所以可以直接判断,指针是否为nullptr。

if(p1)
{}
if(!p1)
{}

解引用

因为重载了operator*()函数,所以可以直接对指针解引用。

*p1;

访问成员

因为重载了operator->()函数,所以可以直接获取对象的成员。

p1->~Test();

下标访问

因为重载了operator[]()函数,如果管理的是数组,则可以通过下标访问元素。

p1[n];

获取/释放

get

使用get()获取管理的对象原生指针,如果unique_ptr为空则返回nullptr。

void test()
{int* pp = nullptr;{auto p = std::make_unique<int>(2);std::cout << *p << std::endl;pp = p.get();}std::cout << *pp << std::endl;	//输出垃圾值
}
release

使用release()可以返回原生指针并释放所有权,智能指针管理的指针会变为nullptr。

🎫 注意: 这个时候就需要自己手动释放内存了。

void test()
{auto p = std::make_unique<int>(23);auto pp = p.release();			//智能指针p不在管理pp对象了 , 释放所有权,并且返回原生指针delete pp;						//需要自己释放内存
}
reset

使用reset()释放unique_ptr管理的对象,有一个可选参数,如果为nullptr,则只会释放对象,并指向nullptr

注意:这个函数是释放unique_ptr对象管理的指针,使其不在管理这个指针,这个时候如果传递了对象指针,则释放之后unique_ptr会接管传递的对象。

void test()
{unique_ptr<int> up;up.reset(new int(-22));		//获取指针的所有权cout << *up << endl;up.reset(new int(666));		//释放管理的内存,获取新的指针cout << *up << endl;up.reset();						//释放管理的内存
}

shared_ptr

unique_ptr是一个独享指针,与同一时刻只能有一个unique_ptr指向一个对象,而shared_ptr是一个共享指针,同一时刻可以有多个shared_ptr指向同一对象;

unique_ptr会记录有多少个shared_ptr共同指向一个对象,这便是所谓的引用计数

🔽 **注意 注意 注意 **:use_count() 记录有多少个shared_ptr共同指向一个对象,但是shared_ptr 不是通过原生指针赋值才使use_count + 1 的,是通过赋值shared_ptr

🎲 一旦某个对象的引用计数变为0,这个对象会被自动删除.

常见错误

  • 用原生指针多次初始化
  • 不明白 shared_ptr 中构造函数中 用了 explicit
  • 用栈指针赋值
  • 使用shared_ptr的get()初始化另一个shared_ptr
  • 作为函数参数传递时,不要乱用引用,因为他本身有引用计数,乱用可能导致指针提前释放

shared_ptr 内部指向两个位置

  • 指向对象的指针;
  • 用于控制引用计数数据的指针。

构造对象

可以使用shared_ptr提供的构造函数构造对象,可以构造对象,也可以构造数组!

std::shared_ptr<Test> sp(new Test);
std::shared_ptr<Test[]> sp1(new Test[5]);

还可以这样:

auto sp2 = std::make_shared<Test>();
/值得会注意的是,使用       make_shared 不支持创建数组
//auto sp3 = std::make_shared<Test>(5);

使用make_shared构造,然后使用auto自动类型推导,就很方便了,这也是本人常用的。

删除器

在某些时候,默认的释放操作不能满足咱们的需要,这个时候就需要自定义删除器。

std::shared_ptr<Test> sp(new Test, [](auto* ptr) {delete ptr; });

指针使用

和unique_ptr一样

获取/释放

use_count(是一个函数)

使用use_count获取所指对象的引用计数。如果这是一个空的shared_ptr,函数返回零。

unique

检查所管理对象是否仅由当前shared_ptr管理。(判断shared_ptr是不是独占的(只有本对象拥有资源的所有权))

get

unique_ptr;

reset

这个和unique_ptr一样

void reset();					//仅释放,(如果有多个shared_ptr共享一个对象,那么就在引用-1,等到为0时,会自动释放资源
void reset(_Ux* _Px);			//先释放,并接管_Px
void reset(_Ux* _Px, _Dx _Dt)	//先释放,并接管_Px,并给_Px指定_Dt删除器

weak_ptr

简介

shared_ptr可以用来避免内存泄漏,可以自动释放内存,但是在使用中可能存在循环引用,使引用计数失效,从而导致内存泄漏的情况。如下代码所示:

class Widget
{
public:Widget() { std::cout << __FUNCTION__ << std::endl; }~Widget() { std::cout << __FUNCTION__ << std::endl; }void setParent(std::shared_ptr<Widget>& parent) { _ptr = parent; }
private:std::shared_ptr<Widget> _ptr;
};int main()
{{std::shared_ptr<Widget> w1 = std::make_shared<Widget>();std::shared_ptr<Widget> w2 = std::make_shared<Widget>();std::cout << w1.use_count() << " " << w2.use_count() << std::endl;w1->setParent(w2);w2->setParent(w1);std::cout << w1.use_count() << " " << w2.use_count() << std::endl;}return 0;
}

输出结果:

Widget::Widget
Widget::Widget
1 1
2 2

我们发现,并没有调用析构函数,也就是对象没有释放,为什么会发生这样的事情呢?

  • w1->_ptr 引用了w2,w2->_ptr 引用了w1,这样就形成了循环引用。
  • 当w1超出作用域,释放的时候,发现管理的对象引用计数不为1,则不释放对象
  • 当w2超出作用域,释放的时候,发现管理的对象引用计数也不为1,也不是放对象
  • 这样就发生了内存泄漏

weak_ptr

  • weak_ptr: 的出现是为了解决,shared_ptr的循环引用问题的,weak_ptr是对shared_ptr 对象的一种弱引用,它不会增加对象的引用计数

  • 循环引用:两个对象,每个对象都有一个shared_ptr成员,他们这个成员相互引用;

  • weak_ptr不会增加shared_ptr的引用次数,weak_ptr只能shared_ptr构造;

  • shared_ptr可以直接赋值给week_ptrweek_ptr可通过调用lock函数来获得shared_ptr

解决循环引用

shared_ptr智能指针的循环引用导致的内存泄漏问题,可以通过weak_ptr解决。只需要将std::shared_ptr<Widget> _ptr改为weak_ptr:

std::weak_ptr<Widget> _ptr;

输出结果为:

Widget::Widget
Widget::Widget
1 1
1 1
Widget::~Widget
Widget::~Widget

weak_ptr函数

  • **use_count():**获取当前观察的资源的引用计数
  • **expired():**判断所观察资源是否已经释放
  • **lock():**返回一个指向共享对象的shared_ptr,如果对象被释放,则返回一个空shared_ptr:

使用场景:weak_ptr不改变其所共享的shared_ptr实例的引用计数,那就可能存在weak_ptr指向的对象被释放掉这种情况。
这时就不能使用weak_ptr直接访问对象。必须用expired()判断weak_ptr指向对象是否存在!

我们需要使用enable_shared_from_this?

  • 需要在对象内部有一个指向当前对象的shared_ptr指针
  • 当一个程序中,类的成员函数需要传递this指针给其他函数时候,如果构造另外一个shared_ptr传进去的化,回出错,因为这个相当于用另外一个智能指针去管理了,不是传递本身的智能指针

什么时候用enable_shared_ptr?

  • 当我们需要在外部多次使用一个相同的shared_ptr时候。
  • 当类的成员函数需要传递this指针给其他函数的时候(这个时候资源共享)
  • 如下:

int main()
{//或直接构造智能指针↓auto obj = std::make_shared<SObject>();//获取指向obj的智能指针auto sp = obj->shared_from_this();  auto sp1 = obj->shared_from_this();std::cout << sp.use_count() << " " << sp1.use_count() << std::endl;if (!sp){std::cout << "is nullptr" << std::endl;}return 0;
}

输出:

1 1   // 共同使用一个shared_ptr

这个需要结合一些项目场景去理解,后面我们会更新aiso网络去构建Tcp服务器的文章,那里会有涉及到这个场景。

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

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

相关文章

Nature Methods | 人工智能在生物与医学研究中的应用

Nature Methods | 人工智能在生物与医学研究中的应用 生物研究中的深度学习 随着人工智能&#xff08;AI&#xff09;技术的迅速发展&#xff0c;尤其是深度学习和大规模预训练模型的出现&#xff0c;AI在生物学研究中的应用正在经历一场革命。从基因组学、单细胞组学到癌症生…

队列-链式描述(C++)

定义 使用链表描述队列时&#xff0c;通常包含以下几个基本要素&#xff1a; 队头指针&#xff08;Front Pointer&#xff09;&#xff1a;指向队列中第一个&#xff08;即最早进入队列的&#xff09;元素的节点。队尾指针&#xff08;Rear Pointer&#xff09;&#xff1a;指…

Flutter 之 InheritedWidget

InheritedWidget 是 Flutter 框架中的一个重要类&#xff0c;用于在 Widget 树中共享数据。它是 Flutter 中数据传递和状态管理的基础之一。通过 InheritedWidget&#xff0c;你可以让子 Widget 在不需要显式传递数据的情况下&#xff0c;访问祖先 Widget 中的数据。这种机制对…

Python 深度学习框架介绍

Python 是深度学习领域的主流编程语言&#xff0c;拥有许多强大的深度学习框架&#xff0c;广泛用于学术研究、工业应用和生产环境中。以下是一些最流行的 Python 深度学习框架&#xff0c;它们各自具有独特的功能和特点&#xff1a; 1. TensorFlow 开发公司&#xff1a;Google…

MySQL技巧之跨服务器数据查询:进阶篇-从A服务器的MySQ数据库复制到B服务器的SQL Server数据库的表中

MySQL技巧之跨服务器数据查询&#xff1a;进阶篇-从A服务器的MySQ数据库复制到B服务器的SQL Server数据库的表中 基础篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MyS…

Flutter 指纹识别

在这篇博客中&#xff0c;我们将介绍如何使用 Flutter 的 local_auth 插件在 Android 和 iOS 设备上实现指纹识别功能。通过这一步一步的实现&#xff0c;我们将学习如何检查设备是否支持生物识别、如何触发指纹验证&#xff0c;并处理可能出现的错误。 效果图&#xff08;因为…

CentOS 9 配置静态IP

文章目录 1_问题原因2_nmcli 配置静态IP3_使用配置文件固定IP4_重启后存在的问题5_nmcli 补充 1_问题原因 CentOS 7 于 2014年6月发布&#xff0c;基于 RHEL 7&#xff0c;并在 2024年6月30日 结束维护。 CentOS 9 作为目前的最新版本&#xff0c;今天闲来闲来无事下载下来后…

数据结构(三)——双向链表的介绍以及实现

前言 前面两期数据结构的文章我们介绍了顺序表和单向链表&#xff0c;那么本篇博文我们将来了解双向链表&#xff0c;作为最好用的一种链表&#xff0c;双向链表有什么特殊之处呢&#xff0c;接下来就让我们一起了解一下吧。 下面是前两篇数据结构的文章&#xff1a; 数据结…

Oracle--表空间Tablespace

在 Oracle 数据库中&#xff0c;表空间&#xff08;Tablespace&#xff09; 是一种逻辑存储结构&#xff0c;用于组织和管理数据库中物理存储数据文件的方式。以下是表空间相关操作的详细介绍&#xff0c;包括创建、修改、删除、查询以及常见问题处理。 1. 表空间的作用 提供逻…

cmake一些常用指令

cmake常用的一些命令 推荐网址&#xff1a;CMake 保姆级教程&#xff08;上&#xff09; | 爱编程的大丙 cmake_minimum_required(VERSION 3.0) project(CALC) # 增加-stdc11 set(CMAKE_CXX_STANDARD 11) # 指定输出的路径 set(HOME ${CMAKE_CURRENT_SOURCE_DIR}) # 可执行文…

面阵相机的使用和注意事项

引言 面阵相机&#xff08;Area Scan Camera&#xff09;是一种广泛应用于工业视觉、医学成像、安防监控以及科研领域的图像采集设备。与线扫相机不同&#xff0c;面阵相机的传感器包含多行像素&#xff08;例如1280x1024、1920x1080等&#xff09;&#xff0c;能够在一个曝光…

损失函数分类

1. NLLLoss&#xff08;负对数似然损失&#xff09; 定义&#xff1a; 直接对预测的概率 p(yi) 的负对数求平均。通常配合 Softmax 使用&#xff0c;输入为对数概率。 优点&#xff1a; 对离散分类问题效果良好。更灵活&#xff0c;用户可以自行计算 Softmax。 缺点&#x…

python冒号是什么意思

例如&#xff1a; user: User User.objects.filter(iddata.get(uid)).first() 变量名后面的冒号是&#xff1a;类型注解&#xff0c;3.6以后加入的&#xff0c;冒号右边是类型&#xff0c;仅仅是注释&#xff0c;有些鸡肋。 变量注释的语法&#xff1a;注释变量类型,明确指出…

ESLint v9.0.0 新纪元:探索 eslint.config.js 的奥秘 (4)

从 v9.0.0 开始&#xff0c;官方推荐的配置文件格式是 eslint.config.js&#xff0c;并且支持 ESM 模块化风格&#xff0c;可以通过 export default 来导出配置内容。 // eslint.config.js export default [{rules: {semi: "error","prefer-const": "…

【Vue3】【Naive UI】< a >标签

【Vue3】【Naive UI】< a >标签 超链接及相关属性其他属性 【VUE3】【Naive UI】&#xff1c;NCard&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;n-button&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;a&#xff1e; 标签 <a> 标签HTML中的一个锚&…

打字指法和键盘按键功能简介

打字指法和键盘按键功能简介 一、打字指法简介&#xff08;附视频演示&#xff09; 基本要领和练习步骤&#xff1a; 手指位置&#xff1a;正常情况下&#xff0c;大拇指放在空格键上&#xff0c;其余四个手指分别放在 ASDF 和 JKL; 键上。 打字姿势&#xff1a;打字时手指…

H3C ACL实验

实验拓扑 实验需求 按照图示配置 IP 地址全网路由互通在 SERVER1 上配置开启 TELNET 和 FTP 服务配置 ACL 实现如下效果 192.168.1.0/24网段不允许访问 192.168.2.0/24 网段&#xff0c;要求使用基本 ACL 实现 PC1 可以访问 SERVER1 的 TELNET 服务&#xff0c;但不能访问 FTP…

【热门主题】000077 物联网智能项目:开启智能未来的钥匙

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

网络安全-网络安全审计

网络安全审计是为了确保网络系统的安全性和完整性&#xff0c;防范潜在的网络攻击和数据泄露风险。 审计步骤&#xff1a; 1.确定审计目标&#xff1a;明确审计的目的和范围&#xff0c;例如审计网络设备、服务器、应用程序或数据库等。 2.收集信息&#xff1a;收集审计范围…

AIGC时代 | 如何从零开始学网页设计及3D编程

文章目录 一、网页设计入门1. 基础知识2. 学习平台与资源3. 示例代码&#xff1a;简单的HTMLCSSJavaScript网页 二、3D编程入门1. 基础知识2. 学习平台与资源3. 示例代码&#xff1a;简单的Unity 3D游戏 《编程真好玩&#xff1a;从零开始学网页设计及3D编程》内容简介作者简介…