首页 > 编程语言 >C++ 左/右值及其引用 论述

C++ 左/右值及其引用 论述

时间:2023-09-20 22:01:59浏览次数:65  
标签:右值 int move 左值 C++ 引用 && 论述

说明: 本文探讨的是 C++11 以来的值类别

关于左值和右值,在不对其进行详细的划分时,简单的分类方法包括

  1. 左值持久,右值短暂
  2. 能取得地址得通常是左值,反之通常是右值(这一方法启示我们一个表达式的类型与其是左值还是右值无关,即相同类型的表达式既可以是左值也可以是右值)

右值引用是必须绑定到右值的引用,通过&&获取右值引用 左值引用如果想绑定要右值上,只能通过const,例如

int i = 1;
int &r = i;           // 正确,i是一个左值,可以采用左值引用
int &r = i * 2;       // 错误,i*2是一个右值,无法直接采用左值引用
const int &r = i * 2; // 正确
int &&r = i * 2;      // 正确,i*2是一个右值,可以采用右值引用

C++11以前,右值被认为是无用的资源,右值引用引入的目的是为了延长用来初始化对象的生命周期(左值的生命周期和作用域相关,无需延长)

int x = 20;   // 左值
int&& rx = x * 2;  // x*2的结果是一个右值,rx延长其生命周期
int y = rx + 2;   // 可以重用右值:42
rx = 100;         // 一旦初始化一个右值引用变量,该变量就成为了一个左值,可以被赋值

以上代码一方面展示了右值引用延长生命周期的效果,同时引出了右值引用一个重要的特点,即初始化之后的右值引用将变成一个左值,如果是non-const还可以被赋值

关于"左值持久,右值短暂"这种说法,存在一个第一次看让人感到惊讶的例子,即

不能将一个右值引用绑定到一个右值引用类型的变量上

int &&r1 = 42; // 正确,字面值常量是右值
int &&r2 = r1; // 错误,表达式r1(变量可以看作只有一个运算对象而无运算符的表达式)是左值

r1虽然是右值引用,但其本质是一个变量,是一个持久值,因此其属于左值,不能直接被右值引用绑定

不能直接将右值引用绑定到左值上,但左值引用可以通过const实现绑定右值,因此C++11引入了新标准库函数move用于显示地将左值转换为对应右值引用类型,实现用右值类型绑定左值

#include <utility>
int &&r1 = 42;            // 42为右值,r1是右值引用类型,本身属于左值
int &&r2 = std::move(r1); // 正确,move将左值转换为右值引用

move函数有几点注意事项

  1. 函数所在头文件为 <utility>
  2. 由于右值引用能接受任意类型参数,为了避免与名为move且单参数的自定义move函数冲突,move并不提供using命名空间声明,必须使用std::move而非move
  3. 引用折叠的存在,使得右值引用可以接受左值和右值,因此std::move可以接受任意参数
  4. 对于上述代码,调用move意味着不能对移后源对象r1的数值做任何假设,不能再读取它的数值,但是可以进行赋值和销毁操作

虽然说move函数叫做移动,但其只是起到转换作用(从实际效果来看,称其为rvalue_cast似乎更合适),其并没有移动什么东西。对于目前"移动"的名称,更可能是因为其告知了编译器某个对象更加适合移动,所以才称其为move

同时有一个易错点是:使用了std::move并不一定会去利用移动构造函数。在下面的例子中,虽然初始化成员属性时采用了右值,但是由于形参string采用了const,因此实际调用的是拷贝构造而非移动构造函数,因此在创建希望能移动对象时,不要声明它们为const

// 
class Annotation {
public:
    explicit Annotation(const std::string text)
    :value(std::move(text))    //“移动”text到value里;这段代码执行起来
    {}

private:
    std::string value;
};

// std::string 类定义
class string {
public:
    string(const string& rhs);  //拷贝构造函数
    string(string&& rhs);       //移动构造函数
};

关于左值引用和右值引用能够接受参数的范围

  1. 当模板参数为左值引用时,只能传递给它的一个左值的实参。
  2. 当模板参数为const左值引用时,传递给它的一个左值的实参/临时对象、字面值等(右值)
  3. 当模板函数参数是一个右值引用时(准确来说是通用引用,形式与右值引用相同),传递给它的实参可以是任意类型

上面提到了"通用引用",通用引用本质上是特定上下文的右值引用,通过类型推导区分左右值得出上下文,并且会发生引用折叠。 发生通用引用有4种情况

  1. 模板实例化
  2. auto类型推导
  3. typedef与别名声明的创建和使用
  4. decltype

通用引用有2个特点

  1. 其形式与右值引用相同,这是由于通用引用本身就是特殊场景下的右值引用
  2. 可接受任意类型的实参 关于第2点,实际是C++两种机制叠加的效果,一是类型推导,二是引用折叠 使用通用引用时,左值实参会被推导为左值引用,右值实参会被推导为非引用,示例如下
Widget widgetFactory();     //返回右值的函数
Widget w;                   //一个变量(左值)
func(w);                    //用左值调用func;T被推导为Widget&
func(widgetFactory());      //用右值调用func;T被推导为Widget

理解引用折叠,需要首先明确以下原则

C++中引用的引用是非法的

此原则并不难理解,如果可以声明引用的引用,那么理论上就可以做到无限引用

int x;
int& &rx = x; // 错误,不能声明引用的引用

在此原则基础上,考虑把一个左值赋值给一个通用引用,在进行类型推导后会被推导为左值引用,此时出现的就是引用的引用,例如以下示例代码

template<typename T>
void func(T&& param);       //同之前一样

func(w);                    //用左值调用func;T被推导为Widget&

以上代码在经历类型推导后,得到的结果是void func(Widget& && param); 显然这是非法的,因此编译器会进行引用折叠 经历了类型推导和引用折叠后,对于原始传入的左值,最终形成了左值引用;对于原始传入的右值,最终形成了右值引用

Reference

标签:右值,int,move,左值,C++,引用,&&,论述
From: https://blog.51cto.com/u_14882565/7543621

相关文章

  • C++中的四种类型转换运算符
    隐式类型转换是安全的,显式类型转换是有风险的,C语言之所以增加强制类型转换的语法,就是为了强调风险,让程序员意识到自己在做什么。但是,这种强调风险的方式还是比较粗放,粒度比较大,它并没有表明存在什么风险,风险程度如何。再者,C风格的强制类型转换统一使用(),而()在代码中随处可见,所以......
  • c++ 访问全局变量
      #include<iostream>usingnamespacestd;inta{1};intmain(){inta{123};cout<<"外部的a:"<<a<<endl;//外部的a:123{cout<<"外部的a:"<<a<<endl;//外部的a:123......
  • C++的构造函数和析构函数
    背景介绍在B站上看完侯捷老师讲解的两个类:String类andcomplex类,这两个类的实现体现了不带指针和带指针的区别,也可以作为设计类的参考学习。这两个类的实现过程中有很多小细节的东西需要注意,否则很可能造成编译报错。编写带指针的类String在c++的ansi库中有有一个string类,用......
  • C++医学影像(PACS)管理系统源码
    PACS(PictureArchivingandCommunicationsSystem)——图像存储与传输系统,和医院信息化及数字化的目标紧密关联,它是专门为现代化医院的影像管理而设计的包括数字化医学图像信息的采集、显示、处理、存储、诊断、输出、管理、查询、信息处理的综合应用系统,是以数字化诊断(无纸化、无......
  • C++ STL 容器之map
    一、map简介可以将任何基本类型映射到任何基本类型。如intarray[100]事实上就是定义了一个int型到int型的映射。map提供一对一的数据处理,key-value键值对,其类型可以自己定义,第一个称为关键字,第二个为关键字的值map内部是自动排序的二、用法1.map定义:map<type1name,t......
  • c++中生成随机数
    #include<iostream>#include<string>#include<algorithm>#include<ctime>usingnamespacestd;constintINF=1e9;intmain(){//设置种子srand((unsigned)time(NULL));//可随机生成0-10以内的数 intt=rand()%10; cout<<......
  • 【结对编程互评-C++】中小学数学卷子自动生成程序
    【结对编程互评-C++】中小学数学卷子自动生成程序项目名称:中小学数学卷子自动生成程序编程语言:C++代码作者:李义评价人:张恒硕目录[1.项目要求][1.1目标用户][1.2实现功能][2.代码分析][3.功能测试][3.1登录功能测试][3.2出题功能测试][4.优缺点分析与总结]......
  • C++学习
    C++简单或复杂又如何,万般皆由人--风尘尘风一、C++简述1.1C++概念C++是一种由BjarneStroustrup于1979年在新泽西州贝尔实验室开始设计开发的高级语言C++扩充和完善了C语言,是面向对象的程序设计语言,C++可运行于多种平台上(Win、Mac、unix)1.2C++特点C++......
  • C++中的类指针
    Studenta;s.setName("A");//Studeng*b=newStudent();Student*b; //声名指针b=newStudent(); //动态分配内存b->setName("B"); //访问成员函数分析定义类对象基本格式是:Studenta;在定义时就已经为a对象分配好了内存空间,且为内存栈;定义类指针......
  • C++文件的读写
    文件读写函数库对于文件对象的操作,主要使用库:#include<fstream>类可以定义三种类对象:ifstream定义的对象只能读文件ofstream定义的对象只能写文件iofstream定义对象既能读文件,也能写文件类定义的对象中open()方法的第二个参数文件模式(filemode)有多种属性:in:......