Rust-析构函数

所谓“析构函数”(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类型的标记,用于标记该对象的析构函数是否已经被调用,生成的代码逻辑像下面这样:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

系列十一、Spring Security登录接口兼容JSON格式登录

一、Spring Security登录接口兼容JSON格式登录 1.1、概述 前后端分离中,前端和后端的数据交互通常是JSON格式,而Spring Security的登录接口默认支持的是form-data或者x-www-form-urlencoded的,如下所示: 那么如何让Spring Securi…

面试狗面试指南系列(1/5): 做好面试需要的一切准备

面试狗,是一群叛逆的程序员开发的远程面试助手,已经帮1000朋友顺利拿到offer! 它可以: 实时识别面试官语音,自动提取关键问题最先进的GPT4加持,按照方便快速阅读的方式高效组织结果,快速展示重…

洗地机哪个品牌的好用?目前口碑最好的洗地机

近年来,随着科技的不断进步和人们对生活质量要求的提高,洗地机已经成为家庭和商业清洁的必备工具之一。但是随之而来的问题是,市面上的洗地机品牌繁多,质量参差不齐,消费者很难在众多选择中找到一款质量好又耐用的产品…

计算机毕业设计 基于Java的手机销售网站的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

2024创业方向,必火的创业项目推荐

有人说我辛辛苦苦的上班还这么穷,这个世界太不公平,但是我要跟你说一个更扎心的事实啊,就是因为你兢兢业业的上班,所以你才这么穷。那么就有人要说了,不上班我能干嘛?这就是大多数人的一个思维,…

阿里巴巴的第二代通义千问可能即将发布:Qwen2相关信息已经提交HuggingFace官方的transformers库

本文来自DataLearnerAI官方网站:阿里巴巴的第二代通义千问可能即将发布:Qwen2相关信息已经提交HuggingFace官方的transformers库 | 数据学习者官方网站(Datalearner) 通义千问是阿里巴巴开源的一系列大语言模型。Qwen系列大模型最高参数量720亿&#xf…

在linux环境下安装lnmp

lnmp官网:https://lnmp.org 一:lnmp安装 参考:https://lnmp.org/install.html 1:下载lnmp安装包 wget https://soft.lnmp.com/lnmp/lnmp2.0.tar.gz -O lnmp2.0.tar.gz 2:解压lnmp安装包 tar zxf lnmp2.0.tar.gz …

JMeter 源码解读HashTree

背景: 在 JMeter 中,HashTree 是一种用于组织和管理测试计划元素的数据结构。它是一个基于 LinkedHashMap 的特殊实现,提供了一种层次结构的方式来存储和表示测试计划的各个组件。 HashTree 的特点如下: 层次结构:Ha…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 第1章 HTML5+CSS3初体验 项目1-2 许愿墙

项目展示 在生活中,许愿墙是一种承载愿望的实体,来源于“许愿树”的习俗。后来人们逐渐改变观念,开始将愿望写在小纸片上,然后贴在墙上,这就是许愿墙。随着互联网的发展,人们又将许愿墙搬到了网络上&#…

hcip-4

ISIS:中央系统到中央系统 基于OSI模型开发; 集成的ISIS,基于OSI开发后转移到TCP/IP模型执行; 故集成的ISIS既可以在OSI模型,也可在TCP/IP模型工作; ISIS是在ISP中使用的一个IGP协议,其归属于无类别链路状…

系统性学习vue-vue核心

做了三年前端,但很多系统性的知识没有学习 还是从头系统学习一遍吧 课程是b站的Vue2.0Vue3.0课程 后续还会学习的如下,就重新开一篇了,不然太长,之后放链接 vue组件化编程 vue-cli 脚手架 vue中的ajax vue-router vuex element-ui vue3 老师推荐的vscode针对vue的插件: Vue 3…

Invalid bound statement (not found)(xml文件创建问题)

目录 解决方法: 这边大致讲一下我的经历,不想看的直接点目录去解决方法 今天照着老师视频学习,中间老师在使用动态SQL时,直接复制了一份,我想这么简单的一个,我直接从网上找内容创建一个好了,…

新能源汽车智慧充电桩方案:如何实现充电停车智慧化管理?

一、方案概述 基于新能源汽车充电桩的监管运营等需求,安徽旭帆科技携手合作伙伴触角云共同打造“智能充电设备+云平台+APP小程序”一体化完整的解决方案,为充电桩车位场所提供精细化管理车位的解决办法,解决燃油车恶意…

推荐几款常用测试数据自动生成工具(适用自动化测试、性能测试)

一、前言 在软件测试中,测试数据是测试用例的基础,对测试结果的准确性和全面性有着至关重要的影响。因此,在进行软件测试时,需要生成测试数据以满足测试场景和要求。本文将介绍如何利用测试数据生成工具来快速生成大量的测试数据。…

【RTOS】快速体验FreeRTOS所有常用API(11)打印空闲栈、CPU占用比

目录 十一、调试11.1 打印任务空闲栈11.2 打印所有任务栈信息11.3 CPU占用比11.4 空闲任务和钩子函数 十一、调试 该部分在上份代码基础上修改得来,代码下载链接: https://wwzr.lanzout.com/in63o1lauwwh 密码:9bhf 该代码尽量做到最简,不添加…

基于ssm的学籍管理系统论文

摘 要 当下,如果还依然使用纸质文档来记录并且管理相关信息,可能会出现很多问题,比如原始文件的丢失,因为采用纸质文档,很容易受潮或者怕火,不容易备份,需要花费大量的人员和资金来管理用纸质文…

༺༽༾ཊ—设计-七个-05-原则-模式—ཏ༿༼༻

第五原则:里氏替换原则 所有基类出现的地方必定能被子类替换,且功能不发生影响 例子:构造函数中参数基类出现的地方 在主类中可以被子类替换,且不改变功能 我们在编写代码时要带有里氏替换原则的思想编写,考虑子类在继…

JVM工作原理与实战(十七):运行时数据区-栈内存溢出

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、Java虚拟机栈 二、栈内存溢出 1.栈内存溢出介绍 2.设置虚拟机栈的大小 总结 前言 ​JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存&…

【计算机网络】内容整理

概述 分组交换 分组交换则采用存储转发(整个包必须到达路由器,然后才能在下一个链路上传输)技术。 在发送端,先把较长的报文划分成较短的、固定长度的数据段。 电路交换 在端系统间通信会话期间,预留了端系统间沿路径通信所需…

基于JavaWeb+BS架构+SpringBoot+Vue智慧党建系统设计与实现

基于JavaWebBS架构SpringBootVue智慧党建系统设计与实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 1 概 述 1 1.1 课题研究背景 1 1.2 课题研究意义 1 1.3 课题研究内容 2 2 系统开…