C++右值引用

C++右值引用技术交流 技术交流 VX Chamfion

右值引用

再说起右值引用之前,我们先了解一下左值引用

int main() { int a = 10; int &b = a; // 定义一个左值引用变量 b = 20; // 通过左值引用修改引用内存的值 return 0; }

上面一段代码的汇编语言如下

int a = 10; // 这条mov指令把10放到a的内存中 00 mov dword ptr [a],0Ah int &b = a; /* 下面的lea指令把a的地址放入eax寄存器 mov指令把eax的内容放入b内存里面 */ 0035421F lea eax,[a] 00 mov dword ptr [b],eax b = 20; /* 下面的mov指令把b内存的值放入eax寄存器(就是a的地址) mov指令再把20放入eax记录的地址的内存里面(就是把20赋值给a) */ 00 mov eax,dword ptr [b] 00 mov dword ptr [eax],14h 

从上面的指令可以看出,定义一个左值引用在汇编指令上和定义一个指针是没有任何区别的,定义一个引用变量int &b=a,是必须初始化的,因为指令上需要把右边a的地址放入一个b的内存里面(相当于定义了一个指针的内存),当给引用变量b赋值时,指令从b里面取出a的地址,并把20写入该地址,也就是a的内存中(相当于给指针解引用赋值),所以也说,使用引用变量时,汇编指令会做一个指针自动解引用的操作。

所以在汇编指令层面,引用和指针的操作没有任何区别!
 

int &b = 20;

 上面的代码是无法编译通过的,现在你应该知道原因,因为定义引用变量,需要取右边20的地址进行存储,但是20是立即数字,没有在内存上存储,因此是无法取地址的,但是解决这个问题还是有办法的,如下:

const int &b = 20;

用常引用可以引用20这个常量数字,难道此时20就能取地址了吗?当然不是,因为现在在内存上产生了一个临时量保存了20,b现在引用的是这个临时量,相当于下面的操作:

/* 这里temp是在内存上产生的临时量 const int temp = 20; const int &b = temp; */ const int &b = 20;

 汇编指令如下:

onst int &b = 20; 010517C8  mov         dword ptr [ebp-14h],14h    《= ebp-14h就是内存栈上产生的临时量的内存地址 010517CF  lea         eax,[ebp-14h]   《= 取临时量的内存地址放入寄存器eax 010517D2  mov         dword ptr [b],eax  《= 再把eax寄存器的值(放的是临时量地址)存入b中 

因此到我们可以得到这样一个结论,上面的C++引用就是我们常用的左值引用,左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用,如const int &b = 20;但是这样一来,我们只能通过b来读取数据,无法修改数据,因为b被const修饰成常量引用了,怎么办?

此时就需要使用C++的右值引用

int &&b = 20; // 通过指令可以看到,原来const int &b=20和int &&b=20一模一样!!! 这里mov指令相当于是产生了临时量,起始地址ebp-14h 00CA18B8 mov dword ptr [ebp-14h],14h 把临时量的地址放入eax寄存器当中 00CA18BF lea eax,[ebp-14h] 再把eax的值(临时量的地址)放入b内存中(一个指针大小的内存) 00CA18C2 mov dword ptr [b],eax b = 40; 00CA18C5 mov eax,dword ptr [b] 00CA18C8 mov dword ptr [eax],28h 

上面代码,定义一个右值引用变量是这样的int &&b=20,从汇编指令来看,依然要产生临时量,然后保存临时量的地址,也就是说const int &b=20和int &&b=20在底层指令上是一模一样的,没有任何区别,不同的是,通过右值引用变量,可以进行读操作,也可以进行写操作。

因此,可以给一个这样的结论可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值。

可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值。

从本质上理解,创建和销毁由编译器幕后控制,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象)。
 

实现一个简单的栈

class Stack { public: // size表示栈初始的内存大小 Stack(int size = 1000) :msize(size), mtop(0) { cout << "Stack(int)" << endl; mpstack = new int[size]; } // 栈的析构函数 ~Stack() { cout << "~Stack()" << endl; delete[]mpstack; mpstack = nullptr; } // 栈的拷贝构造函数 Stack(const Stack &src) :msize(src.msize), mtop(src.mtop) { cout << "Stack(const Stack&)" << endl; mpstack = new int[src.msize]; memcpy(mpstack, src.mpstack, sizeof(int)*mtop); } // 栈的赋值重载函数 Stack& operator=(const Stack &src) { cout << "operator=" << endl; if (this == &src) return *this; delete[]mpstack; msize = src.msize; mtop = src.mtop; mpstack = new int[src.msize]; memcpy(mpstack, src.mpstack, sizeof(int)*mtop); return *this; } // 返回栈的长度 int getSize()const { return msize; } private: int *mpstack; int mtop; int msize; }; Stack GetStack(Stack &stack) { // 这里构造新的局部对象tmp Stack tmp(stack.getSize()); /* 因为tmp是函数的局部对象,不能出函数作用域, 所以这里tmp需要拷贝构造生成在main函数栈帧上 的临时对象,因此这里会调用拷贝构造函数,完成 后进行tmp局部对象的析构操作 */ return tmp; } int main() { Stack s; /* GetStack返回的临时对象给s赋值,该语句结束,临时对象 析构,所以此处调用operator=赋值重载函数,然后调用 析构函数 */ s = GetStack(s); return 0; } 

运行结果如下:

C++右值引用

我们为了解决浅拷贝问题,为类提供了我们自定义的拷贝构造函数和赋值运算符重载函数,并且这两个函数内部实现都是非常的耗费时间和资源(首先开辟较大的空间,然后将数据逐个复制),我们通过上述运行结果发现了两处使用了拷贝构造和赋值重载,分别是tmp拷贝构造main函数栈帧上的临时对象、临时对象赋值给s,其中tmp和临时对象都在各自的操作结束后便销毁了,使得程序效率非常低下。

那么我们为了提高效率,是否可以把tmp持有的内存资源直接给临时对象?是否可以把临时对象的资源直接给s?

在C++11中,即将销毁的对象进行拷贝构造和赋值运算符重载函数,方式是提供带右值引用参数的拷贝构造函数。

// 带右值引用参数的拷贝构造函数 Stack(Stack &&src) :msize(src.msize), mtop(src.mtop) { cout << "Stack(Stack&&)" << endl; /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/ mpstack = src.mpstack; src.mpstack = nullptr; } // 带右值引用参数的赋值运算符重载函数 Stack& operator=(Stack &&src) { cout << "operator=(Stack&&)" << endl; if(this == &src) return *this; delete[]mpstack; msize = src.msize; mtop = src.mtop; /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/ mpstack = src.mpstack; src.mpstack = nullptr; return *this; } 

C++右值引用
Stack(int)
Stack(int)
Stack(Stack&&) =》对应return tmp; 自动调用带右值引用参数版本的拷贝构造
~Stack()
operator=(Stack&&) =》 s = GetStack(s); 自动调用带右值引用参数的赋值重载函数
~Stack()
~Stack()














从上面的打印可以清晰的看到,上面两处的拷贝构造函数和赋值重载函数的调用,自动使用了带右值引用参数的版本,效率大大提升,因为没有涉及任何的内存开辟和数据拷贝。因为临时对象马上就要析构了,直接把临时对象持有的资源拿过来就行了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207116.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午2:24
下一篇 2026年3月19日 下午2:24


相关推荐

  • Linux系统结构详解

    Linux系统结构详解Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。部分层次结构如图1-1所示。1.linux内核Linux内核是世界上最大的开源项目之一,内核是与计算机硬件接口的易替换软件的最低级别。它负责将所有以“用户模式”运行的应用程…

    2022年5月25日
    42
  • datagrid激活码_在线激活

    (datagrid激活码)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/ide…

    2022年3月22日
    299
  • DirectX修复工具使用技巧之一——解除被占用的文件,完整修复C++

    DirectX修复工具使用技巧之一——解除被占用的文件,完整修复C++最后更新:2020-9-23随着V4.0正式版的发布,近来有部分用户来咨询如何删除被占用的C++文件。在此我将以解决最常见的PC版QQ占用的3个C++2010文件(alt100.dll、msvcr100.dll、msvcp100.dll)为例,向大家演示一下操作方法,其他C++或文件的方法大同小异。此次操作以Windows10为例,其他系统相应参考即可。首先,当C++修复失败时,如果想查看具体的错误信息,请首先确定您使用的V4.0增强版或更高版本,老版本不支持此…

    2022年5月25日
    89
  • 憨批的语义分割3——unet模型详解以及训练自己的unet模型(划分斑马线)[通俗易懂]

    憨批的语义分割3——unet模型详解以及训练自己的unet模型(划分斑马线)[通俗易懂]憨批的语义分割3——unet模型详解以及训练自己的unet模型(划分斑马线)学习前言什么是unet模型训练的是什么1、训练文件详解2、LOSS函数的组成训练代码1、文件存放方式2、训练文件3、预测文件训练结果学习前言在这一个BLOG里,我会跟大家讲一下什么是unet模型,以及如何训练自己的unet模型,其训练与上一篇的segnet模型差距不大,但是结构上有一定的差距。什么是unet模型u…

    2022年6月17日
    68
  • linux读取ads1115ADC例程

    linux读取ads1115ADC例程硬件接口 i2c 应用电路程序 ads1115 c include stdio h include stdlib h include sys types h include sys stat h include fcntl h include string h include linux i2c h include linux i2c dev h static linux linux string h fcntl h sys sys stdlib h stdio h

    2026年3月16日
    3
  • 从0到1:C 调用 Claude 插件打通 Excel 与 PowerPoint 工作流

    从0到1:C 调用 Claude 插件打通 Excel 与 PowerPoint 工作流

    2026年3月15日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号