C/C++ noexcept NRVO

news2024/11/21 2:34:37

为什么需要noexcept

为了说明为什么需要noexcept,我们还是从一个例子出发,我们定义MyClass类,并且我们先不对MyClass类的移动构造函数使用noexcept

class MyClass
{
public:
    MyClass()
    {}

    MyClass(const MyClass& lValue)
    {
        std::cout << "拷贝构造函数" << std::endl;
    }

    MyClass(MyClass&& rValue)  // 注意这里,我们没有对移动构造函数使用noexcept
    {
        std::cout << "移动构造函数" << std::endl;
    }

private:
    std::string str{ "hello" };
};

接着,我们创建一个MyClass的对象A,并且将其往classes容器中添加2次

MyClass A{};
std::vector<MyClass> classes;
classes.push_back(A);
classes.push_back(A);

现在,我们来梳理一下流程。classes容器在定义时默认会申请1个元素的内存空间。当第1次执行classes.push_back(A);时,对象A会被拷贝到容器第1个元素的位置:

当第2次执行classes.push_back(A);时,由于classes容器已没有多余的内存空间,因此它需要分配一块新的内存空间。在分配新的内存空间之后,classes容器会做2个操作:将对象A拷贝到容器第2个元素的位置,以及将之前的元素放到新的内存空间中容器第1个元素的位置:

细心的小伙伴一定发现了,如上图所示那般,老的元素是被拷贝到新的内存空间中的。是的,classes容器确实使用的是拷贝构造函数。那么此时我们会想到,既然classes容器已经不需要之前的内存中的数据了,那么将老数据放到新的内存空间中应该使用移动语义,而非拷贝操作。

那么为什么classes容器没有使用移动语义呢?

此时,我们需要提及一个概念,即“强异常保证(strong exception guarantee)”。所谓强异常保证,即当我们调用一个函数时,如果发生了异常,那么应用程序的状态能够回滚到函数调用之前:

那么强异常保证和决定使用移动语义或拷贝操作又有什么关系呢?

这是因为容器的push_back函数是具备强异常保证的,也就是说,当push_back函数在执行操作的过程中(由于内存不足需要申请新的内存、将老的元素放到新内存中等),如果发生了异常(内存空间不足无法申请等),push_back函数需要确保应用程序的状态能够回滚到调用它之前。以上面的例子来说,当第2次执行classes.push_back(A);时,如果发生了异常,应用程序的状态会回滚到第1次执行classes.push_back(A);之后,即classes容器中只有一个元素。

由于我们的移动构造函数没有使用noexcept说明符,也就是我们没有保证移动构造函数不会抛出异常。因此,为了确保强异常保证,就只能使用拷贝构造函数了。那么拷贝构造函数同样没有保证不会抛出异常,为什么就能用呢?这是因为拷贝构造函数执行之后,被拷贝对象的原始数据是不会丢失的。因此,即使发生异常需要回滚,那些已经被拷贝的对象仍然完整且有效。但移动语义就不同了,被移动对象的原始数据是会被清除的,因此如果发生异常,那些已经被移动的对象的数据就没有了,找不回来了,也就无法完成状态回滚了。

为移动语义使用noexcept说明符

在了解了以上的规则后,我们就清楚了,要想使用移动构造函数来将老的元素放到新的内存中,我们就需要告知编译器,我们的移动构造函数不会抛出异常,可以放心使用,这就是通过noexcept说明符完成的。

我们来修改下MyClass类的移动构造函数,为其加上noexcept说明符:

class MyClass
{
public:
    MyClass()
    {}

    MyClass(const MyClass& lValue)
    {
        std::cout << "拷贝构造函数" << std::endl;
    }

    MyClass(MyClass&& rValue) noexcept  // 注意这里,为移动构造函数使用noexcept
    {
        std::cout << "移动构造函数" << std::endl;
    }

private:
    std::string str{ "hello" };
};

现在,我们再次执行上文的例子,会发现使用的是移动构造函数来创建新的内存中的元素了:

关于noexcept说明符,是个庞大的话题,这里我们只是粗略的提及和移动语义有关的部分。值得注意的是,noexcept说明符是我们对于不会抛出异常的保证,如果在执行的过程中有异常被抛出了,应用程序将会直接终止执行。

NRVO

在C++中,存在称为“NRVO(named return value optimization,命名返回值优化)”的技术,即如果函数返回一个临时对象,则该对象会直接给函数调用方使用,而不会再创建一个新对象。听起来有点晦涩,我们来看一个例子:

class MyClass
{};

MyClass GetTemporary()
{
    MyClass A{};
    return A;
}

MyClass myClass = GetTemporary();  // 注意这里

在上面的例子中,GetTemporary函数会创建一个临时的MyClass对象A,接着在函数结束时返回。在没有NRVO的情况下,当执行语句MyClass myClass=GetTemporary();时,会调用MyClass类的拷贝构造函数,通过对象A来拷贝创建myClass对象。

我们可以发现,在创建完myClass对象之后,对象A就被销毁了,这无疑是一种浪费。因此,编译器会启用NRVO,直接让myClass对象使用对象A。这样一来,在整个过程中,我们只有一次创建对象A时构造函数的调用开销,省去了拷贝构造函数以及析构函数的调用开销。

为NRVO点赞!

此时,可能有细心的小伙伴已经发现了,这种返回临时对象的情况不就是移动语义发挥的场景嘛。没错,机智的你是不是会想到如下的修改:

MyClass GetTemporary()
{
    MyClass A{};
    return std::move(A);  // 使用移动语义
}

这样一来,通过移动语义,即使没有NRVO,也可以避免拷贝操作。乍看上去没啥毛病,但我们忽略了一种情况,那就是返回的对象类型并没有实现移动语义。

让我们来分析一下这种情况,我们改写一下MyClass类:

class MyClass
{
public:
    ~MyClass()  // 注意这里,通过声明析构函数,我们禁止了编译器去实现默认移动构造函数
    {}
};

现在,MyClass类型没有实现移动语义,当我们执行语句MyClass myClass=GetTemporary();时,编译器没有办法调用移动构造函数来创建myClass对象。同时,遗憾的是,由于std::move(A)返回的类型是MyClass&&,与函数的返回类型MyClass不一致,因此编译器也不会使用NRVO。最终,编译器只能调用拷贝构造函数来创建myClass对象。

因此,当返回局部对象时,我们不用画蛇添足,直接返回对象即可,编译器会优先使用最佳的NRVO,在没有NRVO的情况下,会尝试执行移动构造函数,最后才是开销最大的拷贝构造函数。

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

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

相关文章

使用语雀绘制 Java 中六大 UML 类图

目录 下载语雀 泛化关系&#xff08;Generalization&#xff09; 实现关系&#xff08;Realization&#xff09; 关联关系&#xff08;Association&#xff09; 依赖关系&#xff08;Dependency&#xff09; 聚合关系&#xff08;Aggregation&#xff09; 组合关系&…

【Python学习】列表和元组

前言 前四天每天更新了小白看的基础教程 今天开始就更新一下&#xff0c;深入一点的知识点吧 还是老话&#xff1a;刚接触python的宝子可以点击文章末尾名片进行交流学习的哦 什么是列表和元组 列表是动态的&#xff0c;长度大小不固定&#xff0c;可以随意地增加、删减或…

【软件测试】软件测试基础2

1. 软件测试的生命周期 软件测试的生命周期&#xff1a; 需求分析→测试计划→ 测试设计、测试开发→ 测试执行→ 测试评估 ● 需求分析&#xff1a;站在用户的角度&#xff1a;查看需求逻辑是否正确&#xff0c;是否符合用户的需求和行为习惯&#xff1b;站在开发人员的角度&…

Nexus使用

环境 apache-maven-3.5.4nexus-3.38.1-01 Android开发中常用的maven代理地址 阿里云&#xff1a;http://maven.aliyun.com/nexus/content/groups/public/google&#xff1a;https://dl.google.com/dl/android/maven2/jcenter&#xff1a;https://jcenter.bintray.com/mavenC…

Leetcode:236. 二叉树的最近公共祖先(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 原理思路: 问题描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&…

Xmanager7远程登录ubuntu20.04

Xmanager7远程登录ubuntu20.04 本文不介绍Xmanager7的下载和安装方法&#xff0c;详细内容可以参考【实用软件】Xmanager 7.0安装教程 - 哔哩哔哩 (bilibili.com)。关于Xmanager7远程登录的教材参考于 (149条消息) Xmanager远程桌面教程_周先森爱吃素的博客-CSDN博客_xmanage…

代码随想录第60天|84.柱状图中最大的矩形

84.柱状图中的最大的图形 总体思路&#xff1a;找到左右两个方向第一个小于该柱子高度的下标&#xff0c;用右下标-左下标-1得到该柱子高度对应的宽度w,再用宽度w*高度h得到面积&#xff0c;返回面积最大值 双指针法&#xff08;超时&#xff09; for循环判断左右第一个小于…

【NI Multisim 14.0虚拟仪器设计——虚拟仪器的引入】

目录 序言 前言 &#x1f349;知识点 一、虚拟仪器的引入 &#x1f34a;1.工具栏 &#x1f34a; 2.基本操作 ①仪器的选用与连接 ②仪器参数的设置 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设计者方便、快捷地使用虚拟元器件和仪器、仪表进行电…

Linux云服务器下的gitee提交代码方法

目录 创建一个gitee仓库 gitee提交代码三板斧 1. git add 提交的文件 2. git commit -m "提交日志" 3. git push 可能存在的问题 .gitignore介绍 如何删除文件 创建一个gitee仓库 gitee提交代码三板斧 1. git add 提交的文件 作用&#xff1a;添加我…

Electron + React 应用打包全流程

&#xff08;第一次用 Typora 写博&#xff0c;希望效果不错~&#xff09; 这几天有个创意编程比赛&#xff0c;要写一个电脑端应用。我准备用 React.js Electron 做&#xff08;因为熟悉~&#xff09;&#xff0c;编程部分一路风雨无阻&#xff0c;到了打包却出现了问题。El…

多轮对话(一):概述(意图识别+槽填充)

一、对话系统 基于流水线的面向任务的对话系统包含了四个关键部分&#xff1a; 语言理解。它被称为自然语言理解&#xff08;NLU&#xff09;&#xff0c;它把用户话语解析为预定义的语义槽。对话状态跟踪器。它管理每一轮的输入与对话历史&#xff0c;输出当前对话状态。对话…

Sechunter移动应用隐私合规检测详解

概述&#xff1a; 受益于移动设备的广泛普及&#xff0c;移动应用近年来得到了蓬勃发展。基于移动设备集成的各类传感器&#xff0c;众多功能丰富的移动应用被开发出来&#xff0c;聚集了大量高价值用户隐私数据&#xff0c;包括用户身份信息、地理位置信息、账户资料信息等。…

玩转机密计算从 secGear 开始

随着网络与计算业务的快速发展&#xff0c;数据成为数字经济的关键生产要素&#xff0c;如何高质量挖掘数据价值&#xff0c;构建安全、合规、可信的数据流通&#xff0c;成为推动数字经济健康发展的关键。此外&#xff0c;根据我国个人信息保护法、欧盟通用数据保护条例 GDPR …

IDEA安装使用代码提交模板

IDEA安装使用代码提交模板 1. idea安装git commit template插件 2、重启idea 3、选择要提交的文件右击&#xff08;或CtrlShiftK&#xff09;&#xff0c;如下图&#xff1a; 3.1 Type of change用于说明commit的类别,常用的标识如下&#xff1a; feat: 新功能&#xff08;f…

501. 二叉搜索树中的众数

501. 二叉搜索树中的众数 难度简单 给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果树中有不止一个众数&#xff0c;可以按 任意顺序 返…

Effective C++条款38:通过复合塑模出 has-a 或“根据某物实现出“

Effective C条款38&#xff1a;通过复合塑模出 has-a 或"根据某物实现出"&#xff08;Model "has-a" or "is-implemented-in-terms-of" through composition&#xff09;条款38&#xff1a;通过复合塑模出 has-a 或"根据某物实现出"1、…

Batch Normalization

1、原理 在图像预处理过程中会对图像进行标准化处理&#xff0c;这样能够加速网络的收敛速度。 如下图所示&#xff0c;对于Conv1来说输入的是满足某一分布的特征矩阵&#xff0c;但对于Conv2来说输入的feature map就不一定满足某一分布规律。 Batch Normalization的目的就是使…

大坝安全在线监控系统包含哪些内容?怎样提升水库大坝信息化管理水平?

平升电子大坝安全在线监控系统根据SL551-2012《土石坝安全监测技术规范》的整编要求&#xff0c;设置了变形监测、渗流监测、环境量监测。大坝安全在线监控系统可及时了解大坝的工作性态和水库可能存在的事故隐患&#xff0c;为大坝安全管理与水库运行调度提供了准确、及时的现…

Elasticsearch集群搭建

前言在如今的开发过程中&#xff0c;单节点的Elasticsearch肯定是支撑不了大数据量的&#xff0c;而且还存在单节点故障的问题&#xff0c;所以Elasticsearch也提供了集群功能&#xff0c;像其他中间件也基本都会考虑到这个问题准备信息首先&#xff0c; 由于我机器有限&#x…

如何在虚拟机上安装Linx系统

前言作为Java开发的我们&#xff0c;可能有时候想自己玩玩linux服务器&#xff0c;但是如果买阿里云或者腾讯云的服务器又很贵&#xff0c;这时候我们就可以在自己电脑上安装虚拟机了&#xff0c;这也是本篇文章出现的原因&#xff0c;下面我就安装centOS7为例子来进行介绍首先…