【C++11】右值引用使用详解

系列文章目录

C++11新特性使用详解-持续更新


文章目录

    • 系列文章目录
    • 前言
    • 一、关联特性
      • 1.1 左值/右值
    • 二、使用方法
      • 2.1 获得右值引用
      • 2.2 对象移动方法
        • 2.2.1 移动构造函数/移动赋值运算符
        • 2.2.2 标记为noexcept
        • 2.2.3 使移动源对象进入是可析构状态
    • 三、使用场景
      • 3.1 移动语义
      • 3.1 完美转发
    • 四、总结


前言

开发中很多情况会发生对象拷贝,在某些情况下,对象拷贝后立即就被销毁了,在这些情况下,从旧内存将元素拷贝到新内存是不必要的。而且C++旧标准中,没有直接的方法可以移动对象,因此不必拷贝的情况下,我们也不得不拷贝。如果对象较大,或者对象本身要求分配内存空间(如string),进行不必要的拷贝代价是非常大的。总之,移动而非拷贝对象会大幅度提升性能。
另外,例如IO类或unique_ptr这样的类,不包含能共享的资源,因此类型的对象不能被拷贝,但可以移动。为了支持移动操作,C++11新标准引入了新的引用类型——右值引用(rvalue references)。所谓右值引用,就是必须绑定到右值的引用。

Note
标准库、string和shared_ptr类既支持移动也支持拷贝。IO和unique_ptr类可以移动但不能拷贝。


一、关联特性

1.1 左值/右值

左值(Lvalue)是指可标识且持久存在的表达式,如变量、函数返回的左值引用等。
右值(Rvalue)是指临时且即将被销毁的表达式,如字面量、临时对象、表达式结果等。

当一个对象被用作右值的时候,用的是对象的值(内容)。 当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)。


二、使用方法

2.1 获得右值引用

通过是std::move函数来获得绑定到左值上的右值引用。
调用std::move意味着:除了对源对象赋值或销毁它外,我们将不再使用它。在调用后,我们不能对移动后源对象的值作任何假设。

应该使用std::move,而不是move。这样可以避免潜在的名字冲突。

2.2 对象移动方法

2.2.1 移动构造函数/移动赋值运算符

类似string类,为了让自己的类也能支持移动,需要为其定义移动构造函数和移动赋值运算符(移动资源而不是拷贝资源)。

2.2.2 标记为noexcept

如果你的类的移动构造函数可以保证不会抛出异常,建议将其声明为 noexcept,以提供额外的异常安全性保证。这可以帮助确保在移动操作失败时,对象仍然处于有效且可用的状态,提高程序的可靠性和健壮性。

  1. 由于移动操作是窃取资源,它通常不分配任何资源。因此移动操作通常不会抛出任何异常。
  2. 当编写一个不抛出异常的的移动操作时,我们应该将此时通知标准库。否则标准库会认为移动我们的类对象可能会抛出异常,并且为了处理这种可能性而做一些额外的工作。
  3. 当移动构造函数声明为 noexcept 时,它表示移动操作不会抛出异常。这可以提供额外的异常安全性保证,称为强异常安全保证(strong exception safety
    guarantee)。
    强异常安全保证要求在移动构造函数中,如果发生异常,对象的状态不会发生改变,即要么移动操作成功完成,要么对象保持在移动之前的状态。这可以确保在移动操作失败时,对象仍然处于有效且可用的状态,不会导致资源泄漏或不一致的状态。
  4. 如果移动构造函数没有声明为 noexcept,则无法提供强异常安全保证。在移动操作中,如果移动构造函数抛出异常,对象可能会处于部分移动的状态,资源可能会泄漏或对象可能会处于不一致的状态。
2.2.3 使移动源对象进入是可析构状态

通过将源对象的指针置为nullptr来实现源对象进入一个可析构状态。

对象移动并不会销毁此对象,但有时在移动操作后,源对象会被销毁。因此我们必须确保源对象进入一个可析构状态。这样即使源对象被析构,也不会释放对象内存。


三、使用场景

右值引用在以下情况下特别有用:

  1. 移动语义:当需要转移资源所有权而不进行深拷贝时,可以使用右值引用来实现高效的移动语义。
  2. 完美转发:当需要将参数以原样传递给其他函数时(同时保留其值类别(左值或右值))和常量性(const限定符),可以使用右值引用来实现完美转发,可以避免不必要的拷贝和类型转换。在泛型编程中特别有用。
  3. 优化性能:通过使用右值引用,可以避免不必要的对象拷贝和内存分配,从而提高代码的性能和效率

3.1 移动语义

class Person
{
public://构造函数Person(std::string name, int age): m_name(name), m_age(age){m_birthgift = new std::string[age];for (int i = 0; i < age; i++) {std::stringstream ss;ss << i+1;std::string str = ss.str() + "Year";m_birthgift[i] = str;}std::cout << "Constructor Called" << std::endl;};//移动构造函数Person(Person&& person) noexcept //成员函数接管源对象person的资源: m_name(person.m_name), m_birthgift(person.m_birthgift), m_age(person.m_age){person.m_name = "";person.m_birthgift = nullptr;   // 令源对象person进入这样的状态-对其运行析构函数时安全的。否则源对象析构的时候,会释放刚移动的内存。会导致和移动后对象有效但无定义。person.m_age = 0;std::cout << "Move Constructor Called" << std::endl;};Person &operator=(Person &&person) noexcept {//直接检查自赋值//检查自赋值的原因:万一指向相同对象,左侧资源释放的时候就当于把右侧资源也释放了,此时左侧资源还没有接管右侧资源。if (this != &person) {//释放移动后对象的已有资源delete[] m_birthgift;//从源对象接管资源m_name = person.m_name;m_age = person.m_age;m_birthgift = new std::string[m_age];for (int i = 0; i < m_age; i++) {std::stringstream ss;ss << i + 1;std::string str = ss.str() + "Year";m_birthgift[i] = str;}// 令源对象person进入这样的状态-对其运行析构函数时安全的。否则源对象析构的时候,会释放刚移动的内存。会导致和移动后对象有效但未定义。person.m_birthgift = nullptr;}return *this;}//析构函数~Person(){delete[] m_birthgift;std::cout << "Destructor Called" << std::endl;};
private:std::string m_name;std::string* m_birthgift;int m_age;
};
int main()
{Person p1("Tom", 20);    //创建一个对象Person p2(std::move(p1));   //使用移动构造函数将p1移动到p2//移动后p1不再指向任何有效的对象,且在移动构造函数中将p1的状态设为无效。不能再对它进行任何操作(访问、修改和销毁)。//输出结果/*Constructor calledMove constructor calledDestructor called       //源对象析构Destructor called       //移动后对象析构*/std::cout << "Hello World!\n";
}

3.1 完美转发

完美转发原理是根据参数的值类别来决定如何转发参数。

// 接受参数的函数,转发右值或左值
void otherFunction(int& arg) {std::cout << "Received lvalue reference: " << arg << std::endl;
}void otherFunction(int&& arg) {std::cout << "Received rvalue reference: " << arg << std::endl;
}// 函数模板,使用完美转发传递参数
template<typename T>
void forwardFunction(T&& arg) {// 在这里可以对参数进行操作std::cout << "Received argument: " << arg << std::endl;// 使用 std::forward 将参数完美转发给其他函数otherFunction(std::forward<T>(arg));
}// 接受 const 参数的函数
void otherFunction1(const int& arg) {std::cout << "Received const lvalue reference: " << arg << std::endl;
}// 函数模板,使用完美转发传递 const 参数
template <typename T>
void forwardFunction1(const T& arg) {// 在这里可以对参数进行操作std::cout << "Received const lvalue reference: " << arg << std::endl;// 使用 std::forward 将参数完美转发给其他函数otherFunction1(std::forward<const T&>(arg));
}
int main()
{/*在函数调用中,传递左值和右值作为参数有一些区别:1. 传递左值:当将左值传递给函数时,函数参数可以是非常量左值引用(T&)或常量左值引用(const T&)。这允许函数修改左值的值或状态。2. 传递右值:当将右值传递给函数时,函数参数可以是非常量右值引用(T&&)或常量右值引用(const T&&)。这允许函数移动或使用右值的值,但不允许修改其值或状态。在完美转发的上下文中,使用 std::forward 可以保留参数的值类别,从而实现对左值和右值的正确传递。通过使用 std::forward,可以将左值作为左值引用传递,将右值作为右值引用传递,从而实现最佳性能和语义。在示例代码中,forwardFunction 使用完美转发将参数传递给 otherFunction。当传递左值时,T 被推导为左值引用类型,从而将参数作为左值引用传递给 otherFunction。当传递右值时,T 被推导为非常量右值引用类型,从而将参数作为右值引用传递给 otherFunction。这样,otherFunction 可以根据参数的值类别进行不同的处理,以实现对左值和右值的正确操作。*/int value = 42;// 传递 lvalueforwardFunction(value);// 传递 rvalueforwardFunction(123);/*在上述示例中,我们定义了一个函数模板 forwardFunction,它接受一个 const 引用参数 arg,并使用完美转发将该参数传递给 otherFunction。在 forwardFunction 中,我们使用 std::forward 来保留参数的 const 限定符*/int value1 = 42;// 传递 const lvalueforwardFunction1(value1);// 传递 const rvalueforwardFunction1(123);}std::cout << "Hello World!\n";
}

四、总结

总结起来,右值引用是 C++11 引入的重要特性,用于提高代码的性能和效率。通过移动语义和完美转发,右值引用可以优化对象的拷贝和内存分配,同时保持代码的简洁性和可读性。在适当的场景下,使用右值引用可以显著改善代码的性能。

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

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

相关文章

中贝通信-603220 三季报分析(20231120)

中贝通信-603220 基本情况 公司名称&#xff1a;中贝通信集团股份有限公司 A股简称&#xff1a;中贝通信 成立日期&#xff1a;1999-12-29 上市日期&#xff1a;2018-11-15 所属行业&#xff1a;软件和信息技术服务业 周期性&#xff1a;1 主营业务&#xff1a;通信网络技术服务…

Qt ListWidget

先创建QListWidgetItem&#xff1a; QListWidgetItem* pListItem1 new QListWidgetItem(QIcon(":/resources/editor.png"),u8"editor");QListWidgetItem* pListItem2 new QListWidgetItem(QIcon(":/resources/env.png"),u8"env");Q…

通信网络安全防护定级备案流程介绍(附流程图)

通信网络安全防护定级备案是拥有增值电信业务经营许可证并且有开展电信业务的企业要做的一件事情。刚接触这块的家人们在填报操作的时候可能对具体通信网络安全防护定级备案流程还不是很清楚&#xff0c;所以就给大家画张具体的流程图吧&#xff0c;可以更加直观的了解。 通信…

go语言学习-go环境安装

1、安装Go 1.1 下载安装 go官网 找对应电脑的版本进行安装即可。 点击安装包&#xff0c;直接下一步下一步即可&#xff0c;安装目录可以自行设置一下。 1.2 验证 windows通过cmd验证。 linux或者mac可以通过自带终端执行测试。 2、配置环境变量 2.1 windows 找到系统…

HarmonyOS开发(四):UIAbility组件

1、UIAbility概述 UIAbility 一种包含用户界面的应用组件用于与用户进行交互系统调度的单元为应用提供窗口在其中绘制界同 注&#xff1a;每一个UIAbility实例&#xff0c;都对应一个最近任务列表中的任务。 一个应用可以有一个UIAbility也可以有多个UIAbility。 如一般的…

Docker 启动alpine镜像中可执行程序文件遇到 not found

## 1. 问题&#xff1a; docker alpine镜像中遇到 sh: xxx: not found 例如&#xff1a; 在容器内/app/目录下放置了可执行文件abc&#xff0c;启动时提示not found /app/startup.sh: line 5: ./abc : not found ## 2. 原因 由于alpine镜像使用的是musl libc而不是gnu libc&am…

深度学习YOLO安检管制物品识别与检测 - python opencv 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov55 模型训练6 实现效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLO安检管制误判识别与检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&…

【论文阅读】SPARK:针对视觉跟踪的空间感知在线增量攻击

SPARK: Spatial-Aware Online Incremental Attack Against Visual Tracking introduction 在本文中&#xff0c;我们确定了视觉跟踪对抗性攻击的一个新任务&#xff1a;在线生成难以察觉的扰动&#xff0c;误导跟踪器沿着不正确的&#xff08;无目标攻击&#xff0c;UA&#x…

设计模式--模板方法外观模式

模板方法模式 场景&#xff1a;需使用代码方式实现&#xff0c;考完试后&#xff0c;将各个学生的试卷及答案誊抄一份。 假如有两个学生的试卷誊抄完毕. // 学生A public class TestPaperA {// 试题1public void testQuestion1() {System.out.println("问题一:XXXXXXXX…

【C++11】Lambda表达式使用详解

系列文章目录 C11新特性使用详解-持续更新 文章目录 系列文章目录简介一、特点二、语法结构三、实例1.排序2.容器赋值3.传参 简介 Lambda表达式是一种用于创建匿名函数的语法结构。它可以在需要函数对象的地方使用&#xff0c;而无需显式定义一个命名函数。Lambda表达式在C中…

【前端学java】Java中的接口和枚举概念(7)

theme: smartblue 往期回顾&#xff1a; 【前端学java】JAVA开发的依赖安装与环境配置 &#xff08;0&#xff09;【前端学 java】java的基础语法&#xff08;1&#xff09;【前端学java】JAVA中的packge与import&#xff08;2&#xff09;【前端学java】面向对象编程基础-类…

自定义函数

Spark自定义函数 spark 中的 UDF (UserDefinedFunction) 大家都不会陌生, UDF 其实就是将一个普通的函数, 包装为可以按 “行“ 操作的函数, 用来处理 DataFrame 中指定的 Columns. 例如, 对某一列的所有元素进行 1 操作, 它对应 mapreduce 操作中的 map 操作. 这种操作有的主…

《opencv实用探索·一》QT+opencv实现图片拼接和Mat转QImage

本文利用opencv实现了几个好用的功能&#xff0c;包含两个文件&#xff0c;如下&#xff1a; 源码放在文章末尾 imageProcessing类包含三个功能&#xff1a; 1、图像拼接 cv::Mat imageMosaic(cv::Mat mat1, cv::Mat mat2, MosaicMode mosaicMode);mat1和mat2为两个待拼接的…

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…

人工智能:科技魔法赋予生活新意

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

自学ansible笔记

一、认识ansible Ansible是一款开源自动化运维工具。它有如下特点&#xff1a; 1、不需要安装客户端&#xff0c;通过sshd去通信&#xff0c;比较轻量化&#xff1b; 2、基于模块工作&#xff0c;模块可以由任何语言开发&#xff0c;比较自由和开放&#xff1b; 3、不仅支持命…

WPF显示3D图形

C# 中的 WPF (Windows Presentation Foundation) 支持显示3D图形。WPF 使用 DirectX 作为底层图形引擎&#xff0c;这意味着它可以处理包括3D图形在内的复杂渲染任务。 在 WPF 中&#xff0c;你可以使用一些内置的类和控件来创建和显示3D对象。这包括 Viewport3D, Camera, Mod…

Android studio run 手机或者模拟器安装失败,但是生成了debug.apk

错误信息如下&#xff1a;Error Installation did not succeed. The application could not be installed&#xff1a;List of apks 出现中文乱码&#xff1b; 我首先尝试了打包&#xff0c;能正常安装&#xff0c;再次尝试了debug的安装包&#xff0c;也正常安装&#xff1…

再谈谷歌GMS认证之Android 13

写在前面的话 2023年来到一个新的公司&#xff0c;传说中的做互联网金融即将上市的高大上公司。 入职后才发现就是做pos机设备的一个小厂 哎&#xff0c;什么命啊&#xff01; 工作和手机开发的工作重合度可以达到95%以上&#xff0c;我不想做手机&#xff0c;偏偏又干上…

计算机基础知识54

ORM的介绍 # ORM是什么&#xff1f; 我们在使用Django框架开发web应用的过程中&#xff0c;不可避免地会涉及到数据的管理操作&#xff08;增、删、改、查&#xff09;&#xff0c;而一旦谈到数据的管理操作&#xff0c;就需要用到数据库管理软件&#xff0c;例如mysql、oracle…