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

文章目录

  • 前言
  • 1. 左、右值的概念
    • 1.1 左值
    • 1.2 右值
    • 1.3 右值引用
  • 2. 右值引用的价值和使用场景
    • 2.1 左值引用的价值和缺陷
    • 2.2 右值引用的价值和使用场景
    • 2.3 小结
  • 3. 完美转发
  • 4. 类的移动构造和移动赋值

前言

在C++11之前,面对C++11之前出现的临时对象的传参构造,都只有老老实实进行深拷贝一份。但是C++11引入一个新的概念和语法特性(右值和右值引用),进而解决临时对象的深拷贝问题等等。

本文章小编主要从以下几个方面来带读者认识右值:

  1. 左、右值的概念
  2. 右值引用的价值和应用场景
  3. 完美转发
  4. 类的移动构造和移动赋值

注:小编的代码环境VS2022。

1. 左、右值的概念

1.1 左值

  • 左值:

    左值是指具有明确存储位置(内存地址)的表达式,通常可以出现在赋值语句的左侧。左值的特点是持久性,即其生命周期超出表达式求值的范围。

    两个特征

    1. 可以取地址。
    2. 可以被赋值。(常变量不可被修改)

例1:

int main()
{int p = 10; //普通整型变量int* a = new int(10); //普通整型指针const int c = 10; //普通常变量"1111111"; //字符串变量return 0;
}

上面代码中的p,a,c,"1111111"都是左值。它们的共同特点都是:可以被取地址

1.2 右值

  • 右值

    右值是指临时对象或没有持久存储位置的表达式,通常只能出现在赋值语句的右侧。右值的特点是短暂性,其生命周期通常仅限于当前表达式。

    两个特征:

    1. 不能取地址。
    2. 不能出现在表达式左边。(特例《C++ Primer》中有提到)

例2:

int main()
{int x = 0, y = 1;
// --------------------10;'c';x + y; //表达式计算结果min(x, y); //函数返回结果return 0;
}

类似于上面的10,'c',x + y, min(x, y)这样的常量或者表达式求值都是属于右值。它们的特点都是:不能被取地址,即没有具体的存储位置!

1.3 右值引用

认识了右值以后,我们来认识右值引用

左值小编已经出过一片文章了:左值引用。这里就不再过多赘诉了。

  • 右值引用:就是对右值取别名。
  • 语法形式:&&。例如:int&& p = 10;这个p就是10的别名。

例3:

int main()
{int a = 10;int& ra = a; //左值引用int&& p1 = 10; //右值引用double x = 1.1, y = 3.3;double&& p2 = x + y; //右值引用return 0;
}

引用都是一样的,都是为左值或者右值取别名(这里有,我们后面在完美转发小节的时候会填)

  • 现在我们思考一个问题

    左值引用可以引用右值吗?右值引用可以引用左值吗?

例4:

#include<iostream>
using namespace std;
int main()
{const int& a = 10; //const左值引用可以引用右值int x = 0;int&& b = std::move(x); //C++标准库中的move函数可以将一个左值返回一个右值return 0;
}

move的语法词典

move这个函数可以将左值转化为右值返回。std::move本质上是一个静态类型转换(static_cast),不涉及任何运行时计算或内存操作。其典型实现如下:

template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept {return static_cast<typename std::remove_reference<T>::type&&>(arg);
}

模板中的T&&代表万能引用,即既可以又来引用左值、也可以用来引用右值。

  • 结论:可以左值引用引用右值,右值引用引用左值。

2. 右值引用的价值和使用场景

进入右值引用的价值之前,我们先来回顾一下左值引用的价值。进而找出左值引用的缺陷,然后再引入右值引用的价值

  • 我们先来看一个前置知识。方便后续的理解

例5:

#include<iostream>
using namespace std;
void func(const int& x)
{cout << "void func(const int& x)" << endl;
}void func(int&& x)
{cout << "void func(int&& x)" << endl;
}int main()
{int x = 10;func(10);func(x);return 0;
}

上面的代码正确吗?即问题是:左值和右值引用能够构成重载吗?

在这里插入图片描述

显然:这里的const T&T&&构成函数重载而且没有调用歧义。在调用的时候,编译器会调用类型更匹配的函数

  • 前置知识:拥有右值属性的值会调用右值属性的参数的函数

2.1 左值引用的价值和缺陷

补充减少拷贝多用于自定义类型中,内置类型拷贝消耗不大。主要考虑自定义类型的拷贝

  • 价值

    1. 做函数参数,减少拷贝。
    2. 做返回值,减少拷贝。
  • 缺陷

    1. 当我们一个函数需要返回一个临时对象的时候,这个时候我们不能返回一个临时对象的左值引用!!!

      结果是未知的。

2.2 右值引用的价值和使用场景

我们再看左值引用的使用缺陷:无法返回一个具有临时性的对象

右值不就是用来引用一个临时性的对象吗?

大致我们能够明白了:右值引用的语法是用来解决左值引用没有解决的历史性问题的!!!

接下来,我们详细探讨一个右值引用的价值(自定义类型string为例)

下面是手写的一个string.h文件,便于我们观察拷贝的细节。

namespace Er //防止命名冲突
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}
  • 我们先聚焦于这两个成员函数
    在这里插入图片描述
    来看下面的场景,例6:
#include"String.h"
#include<iostream>
using namespace std;
Er::string func()
{Er::string str("xxxxx");return str;
}int main()
{Er::string ret;ret = func();return 0;
}

上面代码中如果没有编译器的优化,会进行两次深拷贝(抛开我们实现的swap之外)这样的开始是十分恐怖的!原因就在于我们函数返回的参数是一个右值。我们不得不进行深拷贝。

在这里插入图片描述

对于上面的右值我们有新概念

  • 对于内置类型的右值:纯右值
  • 对于自定义类型的右值:将亡值

如果我们更想减少拷贝,反正将亡值都快要消失了,那么我们是不是可以将将亡值的成员直接给我交换过来?我的数据和其交换,这样效率是否会大大提高?是的,这就是移动语义的构造和赋值的主要思想!!!

此时,String.h中添加两个函数:

// 移动构造
string(string&& s) noexcept:_str(nullptr), _size(0), _capacity(0)
{cout << "string(string&& s) -- 移动语义" << endl;swap(s);
}
// 移动赋值
string& operator=(string&& s) noexcept
{cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;
}

noexcept是表示该函数不会抛异常。swap是自己实现的成员函数Er::string::swap。
由于s是一个将亡值,所以我们直接用this指针指向的内容进行交换,不需要过多的拷贝!!
极大地提高了效率

再来运行上面的例6:

在这里插入图片描述

对面图片中提出的这个问题,如果我们需要右值返回,那么我们返回的值是否需要是一个右值呢?是的,但是str并不是一个右值,相反它是一个左值!!

但是为什么调用了移动构造了呢?
先前也有例子,这里再谈一遍:因为函数返回不是返回的变量,而是返回的是一个临时变量!!
函数返回值是一个临时变量,是一个右值并不是左值

所以总结一下:

  • 使用场景一:右值引用可以用于做函数参数,可用于将亡值对于对象的构造或者赋值。
  • 价值:在接受函数返回值、临时变量的时候减少深拷贝。

例7:

//头文件已包含
int main()
{Er::string s; //正常构造Er::string tmp("aaaaa"); //语句一s = "xxxxxx"; //语句二s = std::move(tmp); //右值的移动赋值return 0;
}

来看运行结果:
在这里插入图片描述

二者都是移动语义

下面进行补充

  • 补充一,关于到底是调用什么拷贝或者赋值函数:

例8:

//头文件已包含
int main()
{Er::string str1("aaaaa"); //语句一//Er::string str2(move("aaaaa")); //语句二 不要对常量进行moveEr::string tmp = "xxxxx";move(tmp);Er::string str3(tmp); //语句三str1 = "xxxxx"; //语句四return 0;
}
  • 前置:字符串常量是一个左值类型。类型为:const char* const

关于运行结果,小编给出提示,读者下来自己理解:

  1. 函数重载,参数传入类型更匹配的地方。解决语句一,语句三。
  2. 隐式类型转化导致的参数发生变化。解决语句四。
  • 补充二,关于move

例9:
在这里插入图片描述

  • 我们应该认识到:move的返回值是一个右值引用,并不是将传入的参数改为右值……

  • 验证:
    在这里插入图片描述

  • 使用场景二:作为函数参数,用于一些常用的接口中。

例如STL中C++11各个容器都添加了新的接口:
在这里插入图片描述
在这里插入图片描述
……

2.3 小结

右值引用的价值和使用场景:

  • 价值:解决了左值引用没有解决的临时对象返回的问题,大大地减少了深拷贝的消耗。

  • 使用场景:

    1. 移动构造和移动赋值
    2. 一些接口的设计

3. 完美转发

在前面1.3右值引用时提到了T&&代表完美引用。同时也可以解决上面在1.3留的一个坑。

来看下面一个场景:

例10:

//头文件已经正确包含
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); //右值int a;PerfectForward(a); //左值PerfectForward(std::move(a));//右值const int b = 8;PerfectForward(b); //const 左值PerfectForward(std::move(b));// const 右值return 0;
}
  • 来看运行结果:
    在这里插入图片描述

    结果全是左值?这是为什么呢???

关于这个问题,我们先回到1.3的例3:

int main()
{int&& p1 = 10; //语句一return 0;
}

对于语句一来说:p1是左值还是右值呢
在这里插入图片描述

右值引用居然是一个左值???

  • 不管是左值引用还是右值引用,形参的属性都是左值属性!!!

如果形参属性不是左值属性,那么我们之前代码所写的swap(s)这样的代码还能成功吗?这也是编译器做出的优化,将右值引用的属性改为左值属性,这样更有利于我们的设计。

那么对于这样的万能引用,我们应该如何来保持其原有属性呢?

这就要讲到我们所用的完美转发了:

  • 函数std::forward。forward词典
    在这里插入图片描述
  • 作用:在传参过程中保持对象原生属性

回到上面的例11:

//其余代码保持不变
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t)); //即可
}

在这里插入图片描述

结果完美正确

所以现在再来看STL设计的容器接口,一定是运用到了完美转发!!!

4. 类的移动构造和移动赋值

  • 针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

    1. 如果你没有自己实现移动构造函数,且没有实现析构函数拷贝构造拷贝赋值重载中的任意一个那么编译器会自动生成一个默认移动构造

      默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

    2. 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。

      默认生成的移动赋值重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

    3. 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

简要概括:

(涉及深拷贝:实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个)、

  1. 不涉及深拷贝的类不需要写,编译器自动提供
  2. 涉及深拷贝的类需要写,编译器不提供。
  3. 一旦自己写了,编译器就不再提供。

完。

  • 小编希望这篇文章能够帮助你!

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

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

相关文章

如何用自指理解世界

自指即自我指涉&#xff0c;即自己的描述关联到了自己&#xff0c;典型例子是“这句话是假话”这个悖论。人类对自指的研究由来已久&#xff0c;很多概念、定理都与之相关&#xff0c;由于它的巧妙性&#xff0c;很多学者对其展开了深入研究&#xff0c;并且认为自指是理解宇宙…

Next.js 实战笔记 2.0:深入 App Router 高阶特性与布局解构

Next.js 实战笔记 2.0&#xff1a;深入 App Router 高阶特性与布局解构 上一篇笔记&#xff1a; Next.js 实战笔记 1.0&#xff1a;架构重构与 App Router 核心机制详解 上篇笔记主要回顾了一些 Next12 到 Next15 的一些变化&#xff0c;这里继续学习/复习一些已有或者是新的…

TCP 传输时 sk_buff 的 clone 和 unclone

周一有位朋友咨询个问题&#xff0c;问题本身不重要&#xff0c;但牵扯出的细节却是非常有趣。 Linux 内核协议栈的 skb 设计非常高效和精巧&#xff0c;多个 skb 可以指向同一块 data&#xff0c;这就是 clone&#xff0c;当 data 不止一个 skb 指示时&#xff0c;任何一个 s…

【51单片机】51单片机学习笔记-课程简介

00. 目录 文章目录00. 目录01. 学习哪种类型的单片机02. 学习单片机方法03. 学习单片机硬件设备04. 学习单片机软件设备05. 学完单片机能做什么06. 附录01. 学习哪种类型的单片机 单片机的型号那么多&#xff0c;该如何选择一款合适的进行学习呢&#xff1f;这里给读者首推的当…

【Docker基础】Docker端口映射(-p参数)深度解析与实践指南

目录 前言 1 Docker网络基础 1.1 Docker网络模型概述 1.2 容器网络隔离性 2 端口映射基础 2.1 端口映射概念 2.2 为什么需要端口映射 3 -p参数详解 3.1 基本语法 3.2 四种映射格式 3.2.1 完整格式 3.2.2 省略宿主机IP 3.2.3 随机宿主机端口 3.2.4 指定协议类型 …

2、鸿蒙Harmony Next开发:ArkTS语言

目录 什么是ArkTS&#xff1f; ArkTS的发展趋势 ArkTS的定位及约束 ArkTS的对UI的拓展 1、UI描述 2、状态管理&#xff1a; ArkTS语法基础 基本知识&#xff1a;声明 基本知识&#xff1a;类型 基本知识&#xff1a;空安全 基本知识&#xff1a;类型安全与类型推断 …

【Elasticsearch】function_score

如果你希望在 Elasticsearch 查询中降低某些特定 `id` 的文档评分,可以通过 `function_score` 查询结合 `script_score` 函数来实现。`script_score` 允许你使用自定义脚本对文档的评分进行调整。 以下是一个示例,展示如何降低某些特定 `id` 的文档评分: 示例场景 假设我们…

vscode打开stm32CubeIDE的项目的注释问题

文章目录 目的是为消除红色底线打开命令面板&#xff1a;CtrlShiftP 搜索并打开&#xff1a;C/C: Edit Configurations (JSON) 修改并添加。&#xff08;注意里面的版本号&#xff09; {"configurations": [{"name": "Win32","includePath&…

ESP32使用freertos更新lvgl控件内容

LVGL不是线程安全&#xff0c;所有 lv_xxx方法只能在GUI主线程调用。 freertos都是线程池&#xff0c;子线程&#xff0c;不能直接更新lvgl&#xff0c;不然看门狗被触发&#xff0c;死机。 推荐方法案例&#xff1a; 假如搜索wifi列表得到参数是wifi_options&#xff0c;需要通…

OBOO鸥柏丨满天星(MTSTAR)多媒体信息发布系统技术解析

初次启动欢迎您使用鸥柏(OBOO)满天星(MTSTAR)多媒体信息发布系统&#xff0c;在使用本系统的独立服务器模式前&#xff0c;我们需要完成设备的一些必须设置教程技术说明。其总体流程分为两步&#xff1a;录入本地服务器IP地址->连接网络您获取到的OBOO鸥柏满天星(MTSTAR)液晶…

数据结构:栈、队列、链表

目录 栈 ​队列 链表 栈 栈数据结构特点&#xff1a;先入栈的数据后出&#xff0c;此数据结构常用的方法有&#xff1a;入栈push、出栈pop、查看栈顶元素peek等&#xff0c;下方示例以数组实现栈结构。 package com.ginko.datastructure; import lombok.extern.slf4j.Slf4j…

Python-难点-uinttest

1 需求要求&#xff1a;unittest.TestCase放在列表中&#xff0c;列表存储的是脚本文件名import使用动态加载方式&#xff1a;importlib.import_module()unittest.TestLoader使用loadTestsFromModule()2 接口3 示例4 参考资料

开源 python 应用 开发(五)python opencv之目标检测

最近有个项目需要做视觉自动化处理的工具&#xff0c;最后选用的软件为python&#xff0c;刚好这个机会进行系统学习。短时间学习&#xff0c;需要快速开发&#xff0c;所以记录要点步骤&#xff0c;防止忘记。 链接&#xff1a; 开源 python 应用 开发&#xff08;一&#xf…

ABP VNext + OpenTelemetry + Jaeger:分布式追踪与调用链可视化

ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追踪与调用链可视化 &#x1f680; &#x1f4da; 目录ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追踪与调用链可视化 &#x1f680;背景与动机 &#x1f31f;环境与依赖 &#x1f4e6;必装 NuGet 包系统架构概览…

C语言中整数编码方式(原码、反码、补码)

在 C 语言中&#xff0c;原码、反码、补码的运算规则与其编码特性密切相关&#xff0c;核心差异体现在符号位是否参与运算、进位如何处理以及减法是否能转化为加法等方面。以下是三者的运算规则及特点分析&#xff08;以 8 位整数为例&#xff0c;符号位为最高位&#xff09;&a…

js二维数组如何变为一维数组

在 JavaScript 中&#xff0c;将二维数组转换为一维数组&#xff08;扁平化&#xff09;有多种方法&#xff0c;可根据数组结构复杂度、性能需求和兼容性选择。以下是最常用的实现方式&#xff1a; 1. 使用 flat() 方法&#xff08;ES2019&#xff09; MDN释义&#xff1a;flat…

Claude code在Windows上的配置流程

前言 昨天在服务器上配置好了 Claude code&#xff0c;发现其编码性能和效率都非常不错。 然而&#xff0c;尝试用它修改带 UI 界面的客户端程序时颇为不便&#xff0c;因为服务器没有图形化界面&#xff0c;无法直接将应用界面直接显示到开发机上&#xff0c;调试起来颇为不…

手把手教你用YOLOv10打造智能垃圾检测系统

无需编程基础&#xff01;手把手教你用YOLOv10打造智能垃圾检测系统 垃圾分类不再难&#xff0c;AI助手秒识别 你是否曾站在分类垃圾桶前犹豫不决&#xff1f;塑料瓶是可回收还是其他垃圾&#xff1f;外卖餐盒到底该丢哪里&#xff1f;随着垃圾分类政策推广&#xff0c;这样的困…

batchnorm类

1. 伪代码&#xff1a;2. python代码&#xff1a;3. 测试&#xff1a;4. 加深理解&#xff1a;以 为例&#xff0c;x3&#xff0c;可见输出的batchnorm后y0.2627.查看模型记录的均值及方差&#xff0c;计算y0.286799&#xff0c;理解是大致这样的计算过程。&#xff08;为什么数…

SpringBoot项目保证接口幂等的五种方法!

1. 幂等概述 1.1 深入理解幂等性 在计算机领域中&#xff0c;幂等&#xff08;Idempotence&#xff09;是指任意一个操作的多次执行总是能获得相同的结果&#xff0c;不会对系统状态产生额外影响。在Java后端开发中&#xff0c;幂等性的实现通常通过确保方法或服务调用的结果…