Rust-析构函数

news2024/11/18 18:27:58

所谓“析构函数”(destructor),是与“构造函数”(constructor)相对应的概念。

“构造函数”是对象被创建的时候调用的函数,“析构函数”是对象被销毁的时候调用的函数。

Rust中没有统一的“构造函数”这个语法,对象的构造是直接对每个成员进行初始化完成的,我们一般将对象的创建封装到普通静态函数中。

相对于构造函数,析构函数有更重要的作用。

它会在对象消亡之前由编译器自动调用,因此特别适合承担对象销毁时释放所拥有的资源的作用。

比如,Vec类型在使用的过程中,会根据情况动态申请内存,当变量的生命周期结束时,就会触发该类型的析构函数的调用。

在析构函数中,我们就有机会将所拥有的内存释放掉。

在析构函数中,我们还可以根据需要编写特定的逻辑,从而达到更多的目的。

析构函数不仅可以用于管理内存资源,还能用于管理更多的其他资源,如文件、锁、socket等。

在C++中,利用变量生命周期绑定资源的使用周期,已经是一种常用的编程惯例。

此手法被称为RAII (Resource Acquisition Is Initialization)。

在变量生命周期开始时申请资源,在变量生命周期结束时利用析构函数释放资源,从而达到自动化管理资源的作用,很大程度上减少了资源的泄露和误用。

在Rust中编写“析构函数”的办法是impl std::ops::Drop。Drop trait的定义如下:
在这里插入图片描述
Drop trait允许在对象即将消亡之时,自行调用指定代码。我们来写一个自带析构函数的类型。示例如下:
在这里插入图片描述
编译,执行结果为:
在这里插入图片描述
从上面这段程序可以看出析构函数的调用时机。

变量_y的生存期是内部的大括号包围起来的作用域(scope),待这个作用域中的代码执行完之后,它的析构函数就被调用;变量_x的生存期是整个main函数包围起来的作用域,待这个函数的最后一条语句执行完之后,它的析构函数就被调用。

对于具有多个局部变量的情况,析构函数的调用顺序是:先构造的后析构,后构造的先析构。因为局部变量存在于一个“栈”的结构中,要保持“先进后出”的策略。

资源管理

在创建变量的时候获取某种资源,在变量生命周期结束的时候释放资源,是一种常见的设计模式。

这里的资源,不仅可以包括内存,还可以包括其他向操作系统申请的资源。

比如我们经常用到的File类型,会在创建和使用的过程中向操作系统申请打开文件,在它的析构函数中就会去释放文件。

所以,RAⅡ手法是比GC更通用的资源管理手段,GC只能管理内存,RAIⅡ可以管理各种资源。

下面用Rust标准库中的“文件”类型,来展示一下RAIⅡ手法。示例如下:

在这里插入图片描述
除去那些错误处理的代码以后,整个逻辑实际上相当清晰:首先使用open函数打开文件,然后使用read_to_string方法读取内容,最后关闭文件,这里不需要手动关闭文件,因为在File类型的析构函数中已经自动处理好了关闭文件这件事情。

再比如标准库中的各种复杂数据结构(如vec linkedList HashMap等),它们管理了很多在堆上动态分配的内存。

它们也是利用“析构函数”这个功能,在生命终结之前释放了申请的内存空间,因此无须像C语言那样手动调用free函数。

主动析构

一般情况下,局部变量的生命周期是从它的声明开始,到当前语句块结束。然而,我们也可以手动提前结束它的生命周期。请注意,用户主动调用析构函数是非法的,示例如下:
在这里插入图片描述

这说明编译器不允许手动调用析构函数。那么,我们怎样才能让局部变量在语句块结束前提前终止生命周期呢?办法是调用标准库中的std::mem::drop函数:
在这里插入图片描述
这段代码会编译出错,是因为调用drop方法的时候,v的生命周期就结束了,后面继续使用变量v就会发生编译错误。

那么,标准库中的std::mem::drop函数是怎样实现的呢?可能许多人想不到的是,这个函数是Rust中最简单的函数,因为它的实现为“空”:

在这里插入图片描述
drop函数不需要任何的函数体,只需要参数为“值传递”即可。将对象的所有权移人函数中,什么都不用做,编译器就会自动释放掉这个对象了。

因为这个drop函数的关键在于使用move语义把参数传进来,使得变量的所有权从调用方移动到drop函数体内,参数类型一定要是T,而不是&T或者其他引用类型。

函数体本身其实根本不重要,重要的是把变量的所有权move进入这个函数体中,函数调用结束的时候该变量的生命周期结束,变量的析构函数会自动调用,管理的内存空间也会自然释放。

这个过程完全符合前面讲的生命周期、move语义,无须编译器做特殊处理。

事实上,我们完全可以自己写一个类似的函数来实现同样的效果,只要保证参数传递是move语义即可。

因此,对于Copy类型的变量,对它调用std::mem::drop函数是没有意义的。下面以整数类型作为示例来说明:

在这里插入图片描述
这种情况很容易理解。因为Copy类型在函数参数传递的时候执行的是复制语义,原来的那个变量依然存在,传入函数中的只是一个复制品,因此原变量的生命周期不会受到影响。

变量遮蔽(Shadowing)不会导致变量生命周期提前结束,它不等同于drop。示例如下:

在这里插入图片描述
编译,执行,输出的结果为:
在这里插入图片描述
这里函数调用的顺序为:先创建第一个x,再创建第二个x,退出函数的时候,先析构第二个x,再析构第一个x。由此可见,在第二个x出现的时候,虽然将第一个x遮蔽起来了,但是第一个x的生命周期并未结束,它依然存在,直到函数退出。这也说明了,虽然这两个变量绑定了同一个名字,但在编译器内部依然将它们视为两个不同的变量。

另外还有一个小问题需要提醒注意,那就是下划线这个特殊符号。请注意:如果你用下划线来绑定一个变量,那么这个变量会当场执行析构,而不是等到当前语句块结束的时候再执行。下划线是特殊符号,不是普通标识符。示例如下:

在这里插入图片描述执行结果为:

在这里插入图片描述
之所以是这样的结果,是因为用下划线绑定的那个变量当场就执行了析构,而其他两个变量等到语句块结束了才执行析构,而且析构顺序和初始化顺序刚好相反。所以,如果大家需要利用RAⅡ实现某个变量的析构函数在退出作用域的时候完成某些功能,千万不要用下划线来绑定这个变量。

最后,请大家注意区分,std::mem::drop()函数和std::ops::Drop::drop()方法。

  1. std::mem::drop()函数是一个独立的函数,不是某个类型的成员方法,它由程序员主动调用,作用是使变量的生命周期提前结束;std::ops::Drop::drop()方法是一个trait中定义的方法,当变量的生命周期结束的时候,编译器会自动调用,手动调用是不允许的。
  2. std::mem::drop(_x:T)的参数类型是T,采用的是move语义;std::ops::Drop::drop(&mut self)的参数类型是&mut Self,采用的是可变借用。在析构函数调用过程中,我们还有机会读取或者修改此对象的属性。

析构标记

在Rust里面,析构函数是在变量生命周期结束的时候被调用的。

然而,既然我们可以手动提前终止变量的生命周期,那么就说明,变量的生命周期并不是简单地与某个代码块一致,生命周期何时结束,很可能是由运行时的条件决定的。

下面用一个示例来说明变量的析构函数调用时机是有可能在运行阶段发生改变的:

在这里插入图片描述

在上面这段示例代码中,我们在变量的析构函数中写了一条打印语句,用于判断析构函数的调用顺序。

在主函数里面,则通过判断当前的环境变量信息来决定是否提前终止某个变量的生命周期。

编译执行,如果我们没有设置DROP环境变量,输出结果为:

在这里插入图片描述
如果我们设置了export DROP=2这个环境变量,不重新编译,执行同样的代码,输出结果为:

在这里插入图片描述
我们还可以将DROP环境变量的值分别改为“1”、“2”、“3”,结果会导致析构函数的调用顺序发生变化。

然而,问题来了,前面说过,析构函数的调用是在编译阶段就确定好了的,调用析构函数是编译器自动插入的代码做的。

而且示例又表明,析构函数的具体调用时机还是跟运行时的情况相关的。那么编译器是怎么做到的呢?

编译器是这样完成这个功能的:首先判断一个变量是否可能会在多个不同的路径上发生析构,如果是这样,那么它会在当前函数调用栈中自动插入一个bool类型的标记,用于标记该对象的析构函数是否已经被调用,生成的代码逻辑像下面这样:

在这里插入图片描述
编译器生成的代码类似于上面的示例,可能会有细微差别。

原理是在析构函数被调用的时候,就把标记设置一个状态,在各个可能调用析构函数的地方都先判断一下状态再调用析构函数。

这样,编译阶段确定生命周期和执行阶段根据情况调用就统一起来了。

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

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

相关文章

软件测试要学习的基础知识——白盒测试

白盒测试是通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试,以确定实际运行状态与预期状态是否一致。 白盒测试又被称为: 透明盒测试 结构化测试 逻辑驱动测试 基于代码的测试 白盒测试的常用技术分类 一、静态分析&#x…

GEE:机器学习分类中每个类别的概率图像可视化

作者:CSDN @ _养乐多_ 在 Google Earth Engine(GEE) 中应用机器学习分类器进行多分类时,有一个需求是想知道每个像素对于每个类别的分类概率。 比如在进行随机森林分类时,每个决策树会生成一个类别,通过投票选择票数最多的类别作为最终分类。除了最终分类结果,其他类别…

【Leetcode】82. 删除排序链表中的重复元素 II

文章目录 题目思路代码 题目 82. 删除排序链表中的重复元素 II 题目:给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 示例 1: 输入:head [1,2,3,3,4,…

Docker容器(二)安装与初体验wordpress

一、安装 1.1关闭SeLinux SeLinux(Security-Enhanced Linux)是一种基于Linux内核的安全模块,旨在提供更严格的访问控制和安全策略。它通过强制实施安全策略来限制系统资源的访问,从而保护系统免受恶意软件和未经授权的访问。 在…

用Photoshop来制作GIF动画

录了个GIF格式的录屏文件,领导让再剪辑下,于是用Photoshop2023进行剪辑,录屏文件有约1400帧,PS保存为GIF格式时,还是挺耗时的,平时少用PS来进行GIF剪辑,编辑后的GIF不能动,网上搜索的…

emacs 源码分析(五)

emacs源码分析(五) 因为emacs的C源码中有大量的宏,在gdb中调试是非常痛苦的,所以有下面的内容: 一个更方便的调试emacs源码的方式 就像在“emacs源码分析(四)”中提到的那样,要么…

内存四区图练习

带着白卡去旅行 绘制图中三种情况的内存四区图 一个实参 一个形参 取地址 通过指针修改变量 返回 多级指针的训练 #define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<stdio.h> #include<string.h> #include<math.h>int getMem(char***p3,…

HDFS WebHDFS 读写文件分析及HTTP Chunk Transfer Encoding相关问题探究

文章目录 前言需要回答的首要问题DataNode端基于Netty的WebHDFS Service的实现基于重定向的文件写入流程写入一个大文件时WebHDFS和Hadoop Native的块分布差异 基于重定向的数据读取流程尝试读取一个小文件尝试读取一个大文件 读写过程中的Chunk Transfer-Encoding支持写文件使…

Transformer从菜鸟到新手(七)

引言 上篇文章加速推理的KV缓存技术&#xff0c;本文介绍让我们可以得到更好的BLEU分数的解码技术——束搜索。 束搜索 我们之前生成翻译结果的时候&#xff0c;使用的是最简单的贪心搜索&#xff0c;即每次选择概率最大的&#xff0c;但是每次生成都选择概率最大的并不一定…

Spring Cloud中的提供者与消费者

在服务调用关系中&#xff0c;会有两个不同的角色&#xff1a; 服务提供者&#xff1a;一次业务中&#xff0c;被其它微服务调用的服务。&#xff08;提供接口给其它微服务&#xff09; 服务消费者&#xff1a;一次业务中&#xff0c;调用其它微服务的服务。&#xff08;调用…

YUM仓库和NFS共享

目录 一、yum仓库 1. yum仓库介绍 1.1 简介 1.2 实现过程 1.3 实现安装服务 2. yum配置文件及命令 2.1 yum配置文件 2.1.1 yum主配置文件 2.1.2 仓库设置文件 2.1.3 日志文件 2.2 yum命令详解 2.2.1 查询 2.2.2 yum安装升级 2.2.3 软件卸载 3. 搭建仓库的方式 …

网卡唯一标识你了解吗?MAC地址详解

本文内容&#xff1a; MAC地址概述 MAC地址组成 单播、组播、广播MAC地址 本地管理和全球管理MAC地址 一、MAC地址概述 MAC地址&#xff08;Media Access Control Address&#xff09;的全称叫做媒体访问控制地址&#xff0c;也称作局域网地址&#xff0c;以太网地址或者物…

Node cool 跨域问题的解决

1.问题 自己在写后端接口的时候 发现一个接口在抖音小程序上可以调用 浏览器上也可以直接打开 但是在H5 的请求中 一直就是cors error 前端报这个跨域问题 在后端 报not Found 一开始以为是找不到 经过确定 发现是跨域问题 2.解决 在全局 configuration.ts 文件里有个全局…

Miracast手机高清投屏到电视(免费)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Miracast概述 Miracast是一种无线显示标准&#xff0c;它允许支持Miracast的设备之间通过Wi-Fi直接共享音频和视频内容&#xff0c;实现屏幕镜像或扩展显示。这意味着你可以…

【linux】终端发送网络请求与文件下载

发送网络请求 linux的终端中发送网络请求可以使用curl命令。 语法&#xff1a; curl [url] 但是他返回的是html代码&#xff0c;因为在终端中&#xff0c;他无法像浏览器中一样把访问到的html代码渲染成我们访问的页面&#xff0c;所以我们只能拿到他的源码。 访问CSDN - 专…

IDEA中如何让包名一层层展开的设置

在开发过程中&#xff0c;发现新下载的一个项目在打开时候&#xff0c;呈现的包是没有一层一层展开的&#xff0c;是平铺在idea中的&#xff0c;截图如下&#xff1a; 设置方法&#xff1a; 打开options的小图标 在treeAppear中的Flatten packages的对号取消掉取消之后的效果…

class_1:qt的安装及基本使用方式

一、选择组件&#xff1a; 1、windows编译工具&#xff1a;MinGW 7.30 32-bit MinGW 7.30 64-bit 2、QT源代码&#xff1a;sources 3、QT的绘图模块&#xff1a;QT charts 4、QT虚拟键盘&#xff1a;QT Virtual Keyboard 5、QT Creational 4.12.2 GDB 二、新建QT项目 文…

DataFunSummit:2023年云原生大数据峰会:核心内容与学习收获(附大会核心PPT下载)

随着数字化转型的深入推进&#xff0c;大数据技术已经成为企业获取竞争优势的关键因素之一。本次峰会汇聚了业界顶尖的大数据专家、企业领袖和技术精英&#xff0c;共同探讨云原生大数据领域的最新技术和趋势。本文将深入分析峰会的核心内容&#xff0c;并探讨参会者从中能学到…

web系统设计安全性基本要求

接口设计安全 身份鉴别 独立的登录模块&#xff1a;为社会用户和平台运营管理用户提供独立的登录地址、登录界面和身份认证模块&#xff0c;通过防火墙等设备严格限制能够登录WEB应用的用户地址、身份&#xff1b; 双因素认证&#xff1a; 平台运营管理人员&#xff1a;采用用…

C++特殊类设计类型转换

一、特殊类设计 在普通类的设计基础上&#xff0c;提出一些限制条件设计的类就是特殊类。 1、请设计一个类&#xff0c;不能被拷贝 拷贝只会放生在两个场景中&#xff1a;拷贝构造函数以及赋值运算符重载&#xff0c;因此想要让一个类禁止拷贝&#xff0c; 只需让该类不能调…