前几天看了一篇文章《4行代码看看右值引用》 觉得写得不错,但是觉得右值引用的内容还有很多可以去挖掘学习,所以总结了一下,希望能对右值引用有一个更加深层次的认识

一、几个基本概念

1.1左值和右值

左值和右值的区分标准在于能否获取地址。

最早的c++中,左值的定义表示的是可以获取地址的表达式,它能出现在赋值语句的左边,对该表达式进行赋值。但是修饰符const的出现使得可以声明如下的标识符,它可以取得地址,但是没办法对其进行赋值:

const int& i = 10;

右值表示无法获取地址的对象,有常量值、函数返回值、Lambda表达式等。无法获取地址,但不表示其不可改变,当定义了右值的右值引用时就可以更改右值。

1.2 左值引用和右值引用

传统的c++引用被称为左值引用,用法如下:

int i = 10;
int & ii = I;

C++ Primer Plus 第6版18.1.9中说到,c++11中增加了右值引用,右值引用关联到右值时,右值被存储到特定位置,右值引用指向该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置。语法如下:

int && iii = 10;

1.3 左值引用和右值引用的汇编代码

以下汇编都是x86汇编

写一段简单的语句,看其汇编

int i = 1;
int & ii = i;

0x080483f3  movl   $0x1,-0x10(%ebp)
0x080483fa  lea    -0x10(%ebp),%eax
0x080483fd  mov    %eax,-0x8(%ebp)

第一句是将1赋值给i,第二句将i的地址放入eax中,第三句将eax中的值传给ii。可见引用就是从一个变量处取得变量的地址,然后赋值给引用变量。

再看一句右值引用的汇编

int && iii = 10;

0x08048400  mov    $0xa,%eax
0x08048405  mov    %eax,-0xc(%ebp)
0x08048408  lea    -0xc(%ebp),%eax
0x0804840b  mov    %eax,-0x4(%ebp)

第一句将10赋值给eax,第二句将eax放入-0xc(%ebp)处,前面说到“临时变量会引用关联到右值时,右值被存储到特定位置”,在这段程序中,-0xc(%ebp)便是该临时变量的地址,后两句通过eax将该地址存到iii处。

通过上述代码,我们还可以发现,在上述的程序中-0x4(%ebp)存放着右值引用iii,-0x8(%ebp)存放着左值引用,-0xc(%ebp)存放着10,而-0x10(%ebp)存放着1,左值引用和右值引用同int一样是四个字节(因为都是地址)

同时,我们可以深入理解下临时变量,在本程序中,有名字的1(名字为i)和没有名字的10(临时变量)的值实际是按同一方式处理的,也就是说,临时变量根本上来说就是一个没有名字的变量而已。它的生命周期和函数栈帧是一致的。也可以说临时变量和它的引用具有相同的生命周期。

1.4 const左值引用

如果写如下代码,定义一个左值引用,将其值置为一个常量值,则会报错:

int & i = 10;

原因很明显,左边是一个左值引用,而右边是一个右值,无法将左值引用绑定到一个右值上。

但是如果是一个const的左值引用,是可以绑定到右值上的。即如下写法是符合语法规范的:

const int & i = 10;

这段程序的汇编代码如下:

0x08048583  mov    $0xa,%eax
0x08048588  mov    %eax,-0x8(%ebp)
0x0804858b  lea    -0x8(%ebp),%eax
0x0804858e  mov    %eax,-0x4(%ebp)

易知-0x4(%ebp)处存放着i,-0x8(%ebp)处则存放着临时对象10,程序将10的地址存放到了i处。看到这里会发现const引用在绑定右值时和右值引用并没有什么区别。

1.5 左值引用和右值引用的相互赋值

能将右值引用赋值给左值引用,该左值引用绑定到右值引用指向的对象,在早期的c++中,引用没有左右之分,引入了右值引用之后才被称为左值引用,所以说左值引用其实可以绑定任何对象。这样也就能理解为什么const左值引用能赋予常量值。

int&& iii = 10;
int& ii = iii;      //ii等于10,对ii的改变同样会作用到iii

二、右值引用和移动语义

在旧的c++中,出现了很多的不必须要的拷贝,因为在某些情况下,对象拷贝完之后就下来就销毁了。新标准引入了移动操作,减少了很多的复制操作,而右值引用正式为了支持移动操作而引入的新的引用类型。

2.1 标准库move函数

根据右值引用的语法规则可知,不能将右值引用绑定到一个左值上,c++11引入右值引用,并且提供了move函数,用来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

Int &&iii = move(ii)

调用move之后,必须保证除了对ii复制或销毁它外,我们将不再使用它,在调用move之后,我们不能对移动源后对象做任何假设。

2.2 模板实参推断和引用

为了理解move函数的实现,首先需要理解模板实参推断和引用。

当左值引用作为参数时,看几个例子:

template<class T> void f1(T&) {}
f1(i)           //i是一个int,模板参数类型T是int
f1(ci)         //ci是一个const int,模板参数T是const int
fl(5)           //错误:传递给一个&参数的实参必须是一个左值

如果函数的参数是const的引用时:

template<class T> void f2(const T&) {}
f2(i)           //i是一个int,模板参数类型T是int,因为非const可以转化为const
f2(ci)         //ci是一个const int,模板参数T是int
f2(5)          //看前面,const的引用可以绑定右值,T是int

当参数是右值引用时,

template<class T> void f3(T&&) {}
f3(5)          // T是int

2.3 引用折叠和右值引用参数

按照道理来说,f3(i)是应该不正确的,因为无法将右值引用绑定到一个左值上,但是,c++中有两个正常绑定规则的例外,允许这种绑定。这两个例外规则是move正确工作的基础

例外1右值引用的类型推断。当我们将一个左值传递给函数的右值引用作为参数时(函数参数为T&&),编译器推断模板类型参数为实参的左值引用类型,,因此,调用f3(i)时,T被推断为int&,而不是int。并且,模板函数中对参数的改变会反映到调用时传入的实参。

通常,我们不能直接定义一个引用的引用,但是同过类型别名(使用typedef)和模板间接定义是可以的。

例外2引用折叠。当定义了引用的引用时,则这些引用形成了“折叠”,所有的情况下(除了一个例外),引用会折叠成一个普通的左值引用类型。这个例外就是右值引用的右值引用:

l  X&  &&、X&  &&、X&&  &都折叠成X&

l  类型X&&  &&折叠成X&&

2.4 理解右值引用折叠和右值引用类型推断

对于函数f3而言,根据右值引用类型推断规则可以知道如下结果:

f3(i)              //实参是左值,模板参数T是int&
f3(ci)            //实参是左值,模板参数T是一个const int&

但是当T被推断为int&时,函数f3会实例化成如下的样子:

void f3<int&>(int&  &&)

然后根据右值引用折叠规则可以知道,上述实例化方式应该被折叠成如下样子:

void f3<int&>(int&)

这两个规则导致了两个重要的结果:

l  如果一个函数参数是一个指向模板类型参数的右值引用,如T&&,则它能被绑定到一个左值,且

l  如果实参是一个左值,则推断出的模板实参类型将时一个左值引用,且函数参数被实例化为一个普通左值引用参数(T&)

值得注意,参数为T&&类型的函数可以接受所有类型的参数,左值右值均可。在前面,同样介绍过,const的左值引用做参数的函数同样也可以接受所有类型的参数。

 

2.5 当右值引用作为函数模板参数时

通过前面,我们了解到当右值引用作为函数模板参数时,类型T会被推断为一个引用类型。这一特性会影响模板函数内部的代码,看下面一段代码:

template<class T>
void f3(T&& val)
{T t = val;t = fcn(t);if(val == t){…}
}

假如以左值i来调用该函数,那么T被推断为int&,将t绑定到val之上,对t的更改就被应用到val,则if判断条件永远为true。

右值引用通常用于两种情况,模板转发其实参、模板被重载。下面都会介绍到

前面说到,const左值引用做参数和右值引用做参数一样,是可以匹配所有的参数类型,但当重载函数同时出现时,右值引用做参数的函数绑定非const右值,const左值引用做参数的函数绑定左值和const右值(非const右值就是通过右值引用来引用的右值,虽然无法获取右值的地址,但是可以通过定义右值引用来更改右值):

Template<class T> void f(T&&)                          //绑定到非const右值
Template<class T> void f(const T&)                  //左值和const右值

2.6 move函数实现

vs2017中move函数的定义如下

using remove_reference_t = typename remove_reference<_Ty>::type;
template<class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
template<class _Ty>
struct remove_reference
{   // remove reference
using type = _Ty;
};template<class _Ty>
struct remove_reference<_Ty&>
{   // remove referenceusing type = _Ty;
};template<class _Ty>
struct remove_reference<_Ty&&>
{   // remove rvalue referenceusing type = _Ty;
};

使用右值引用作为参数,前面说过,可以匹配所有类型。以下两种方式都是正确的:

string s1(“s1”),s2
s2 = move(string(“bye!”))        //_Ty推断为string
s2 = move(s1)                   //_Ty推断为string&

至于remove_reference就好理解了

综上,可以发现move函数不管传入什么类型参数,不管是左值还是右值,都会返回其右值引用类型

三、转发

某些函数需要将其中一个或多个实参连同类型不变地转发给其他函数,在这种情况下,我们需要保持被转发实参的所有性质,包括实参是否是const的、以及是左值还是右值。

有如下的两个函数,在flip中调用f:

void f(int v1, int &v2) {cout << v1 << " " << ++v2 << endl;
}
template <typename F, typename T1, typename T2>
void flip(F f, T1 t1, T2 t2) {f(t2, t1);
}

我们会发现f会改变第二个参数的值,但是通过flip调用f之后就不会改变

f(42, i)
flip(f, j, 42)

模板被实例化成如下:

void flip (void(*fcn)(int, int&), int t1, int t2);

j的值被拷贝到t1中,所以flip中的f只会改变t1,而不会改变j

3.1 定义能保持类型信息的函数参数

如果将flip的参数定义成右值引用,根据上面描述过的规则,当给flip传入引用时,T1被推断为int&,t1则被折叠成int&,完美保持了实参的类型。

template <typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2) {f(t2, t1);
}

但是当函数f接受右值引用作为参数的时候,flip就不能正常工作了

void f(int &&I, int j)
{cout << i << “ ” << j << endl;
}
flip(g,i,42)        //错误,不能从一个左值实例化int&&

注意,这里的f和g都不是模板函数,所以说前面提到的右值引用作为参数时的两个例外不能成立。所以这里是错误的

 

3.2 使用forward保持类型信息

Forward需要显示提供实参类型,返回该实参类型的右值引用(在前面可以看到,右值引用是可以赋值给左值引用的)

void flip(F f, T1&& t1, T2&& t2) {f(forward<T2>(t2), forward<T1>(t1));
}

当显式实参T是int&&时,forward返回int&& &&,折叠成int&&。当当显式实参T是int&时,forward返回int& &&,折叠成int&。所以说forward完美保持了参数的类型。

转载于:https://www.cnblogs.com/likaiming/p/9045642.html

c++右值引用以及使用相关推荐

  1. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  2. C++右值引用与转移和完美转发

    C++右值引用与转移和完美转发 1.右值引用 1.1右值 lvalue 是 loactor value 的缩写,rvalue 是 read value 的缩写 左值是指存储在内存中.有明确存储地址(可 ...

  3. C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符

    前面两节,说明了右值引用和它的作用.下面通过一个string类的编写,来说明右值引用的使用. 相对于C++98,主要是多了移动构造函数和移动赋值运算符. 先给出一个简要的声明: class Strin ...

  4. C++右值引用 和 std::move()

    void debug(int&& a){return a; } std::move(a) 可以将左值变为右值 int c = 0x100; // c 可以被取地址,c 为左值,c++ ...

  5. [译]详解C++右值引用

    2019独角兽企业重金招聘Python工程师标准>>> C++0x标准出来很长时间了,引入了很多牛逼的特性[1].其中一个便是右值引用,Thomas Becker的文章[2]很全面的 ...

  6. 右值引用 移动构造函数 移动语义

    一篇讲的很好的博客:https://www.jianshu.com/p/d19fc8447eaa 左值 右值: 看能不能对表达式取地址,如果能,则为左值,否则为右值. 将亡值和纯右值: 左值 右值: ...

  7. 39.左值、左值引用、右值、右值引用

    1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体:          右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以在CPU寄存器.       ...

  8. std::move C++11 标准新特性: 右值引用与转移语义

    新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...

  9. 移动语义-右值引用-完美转发-万字长文让你一探究竟

    C++ 右值引用 block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38 ...

  10. boost::callable_traits添加member右值引用的测试程序

    boost::callable_traits添加member右值引用的测试程序 实现功能 C++实现代码 实现功能 boost::callable_traits添加member右值引用的测试程序 C+ ...

最新文章

  1. 基于空间数据库MongoDB实现全国电影票预定系统
  2. WPF开发中遇到的问题及解决系列(一):How can I programmatically click a Button
  3. 如何:创建公钥/私钥对
  4. VMware Pro 14.1.2 官方正式版及激活密钥
  5. Android学习笔记(24):进度条组件ProgressBar及其子类
  6. 为什么说干实业的玩不过干金融的?
  7. 微信公众号模板消息 access_token missing
  8. STM32 OSAL操作系统抽象层的移植
  9. pyecharts基础图表汇总(三国演义可视化)
  10. 4k视频写入速度要求_雷克沙高速SD存储卡允许4K视频拍摄
  11. 基于python的opencv图像处理对交通路口的红绿灯进行颜色检测,无人汽车驾驶第一步!
  12. android重力传感器横竖反,Android重力传感器--随重力旋转的图标
  13. 初中物理60个重要知识点
  14. FastDFS集群tracker实现负载均衡
  15. 【FS96生物医学工程学】生物医学工程复试问题
  16. TP4333TPOWER应急灯同步充放移动电源IC解决方案
  17. 搭建一个属于自己的独立线上商城网站
  18. XML文件编写详细过程
  19. 自己搭建一个语音聊天室
  20. 图灵社区 : 阅读 : [讨论] Java语言被很多人抱怨语法繁琐、开发效率低、体系繁杂而笨重,为什么还有这么强的生命力,尤其是在企业软件领域?...

热门文章

  1. ExtJS使用总结和参考
  2. 利用Split函数进行多关键字检索
  3. 福布斯评最佳与最差老板 TCL上黑榜
  4. Python中threading的join和setDaemon的区别及用法[例子]
  5. python标准库学习笔记
  6. 企业如何开展数据可视化建设
  7. Web报表工具FineReport二次开发JS之字符串
  8. Adobe Flex Application LifeCycle
  9. 手动创建一个标准web工程
  10. Pandas时间索引的骚操作