对博客《从4行代码看右值引用》的学习笔记

原文讲的很好,很详细,这里我整理出关键的知识点作为复习用。

结合博客《C++:模板实参推断及引用折叠》学习

https://blog.csdn.net/sixdaycoder/article/details/46489891

为什么要引入右值这个新特性?

 

理清楚概念:左值、右值、纯右值、字面量、将亡值、临时值、左值引用类型、右值引用类型、未定的引用类型(universal references)、引用折叠、移动语义(移动构造)、move语义,完美转发(移动赋值)。

所有的具名变量或对象都是左值,而匿名变量则是右值。

区分左值和右值的一个简单办法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。

在C++11中所有的值必属于左值、将亡值、纯右值三者之一。其中将亡值、纯右值归属于右值。

将亡值是C++11新增的、与右值引用相关的表达式,比如,将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值等。

1、概念

左值和右值

(1)左值

int i = 0; //i是左值,0是字面量(纯右值)

int& j = i; //对左值i进行了左值引用操作,引用到变量j

int& 表示左值引用类型,j表示左值引用类型的变量j的类型为左值引用

(3)右值

int&& i = 0; //对(纯)右值0进行了右值引用操作,引用到变量i

int&& 表示右值引用类型, i表示右值引用类型的变量i的类型为右值引用。

右值是不具名字的,即再程序中看不到对它的指代名,我们只能通过引用来找到它的存在,比如上面的0是(纯)右值,如果不对它进行引用或者赋值,我们的程序将无法对这个0(数据)进行操作。

这里要再强调一下,避免记混,变量的类型和它是否是左值还是右值没有必然联系。在C++11中所有的值必属于左值、将亡值、纯右值三者之一。比如 int&& i = 0; 中,i的类型是右值引用类型int&&,但i本身是左值(因为具名),只是它对(纯)右值0进行了引用。同理,int& j = i; 中,j是左值引用类型,j本身是左值,i也是左值(因为具名),并不是说在等号左边的就是左值,在等号右边的就是右值。

int i = getVar(); //第一行代码

i是左值,函数getVar()返回的是临时值,这个临时值在表达式结束后就销毁了,这个临时值是一个纯右值,把它复制给左值i就后就销毁了(在构造函数结束后里会被析构)。

(3)字面量

int a; //a变量

const int b=10; //b为常量,10为字面量

string str="hello world"; //str为变量,hello world为也字面量

 

2、临时右值

int&& k = getVar(); //第二行代码

对getVar()函数的临时值进行右值引用操作,和上面的第一行代码相比,这里的临时值再函数结束返回后不会被销毁(在构造函数结束后里会不被析构),它被引用到k,得以续命,和变量k的生命周期一样长。

在相同的情况下,A&& a = GetA(); 要比 A a = GetA(); 少一次对临时对象的构造和析构,A是类,a是对象,GetA()表示获取类A的成员变量a。

 

3、常量左值引用

常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值。需要注意的是普通的左值引用只能接受左值。

const A& a = GetA(); //常量左值引用能接受右值,合法

A& a = GetA(); //普通的左值引用不能接受右值,非法

 

int a;

int& b = a; //普通的左值引用能接受左值,合法

 

4、未定的引用类型(universal references)

右值引用的变量(比如下面代码的a)最后的类型可能是左值引用类型也可能是右值引用类型。

template<typename T> void f(T&& a);

T&& 是未定的引用类型,a是未定的引用类型的变量

 

5、引用折叠

到现在已经知道有三种引用类型:左值引用类型、右值引用类型、未定的引用类型。

像T&& 这种经过类型推导才能确定左值引用类型或者右值引用类型的过程叫作引用折叠。

这里要先明白右值转化规则和折叠规则。

在发生自动类型推断的时候,由于存在T&& 这种未定的引用类型,要根据右值转化规则和折叠规则来确定它到底是左值应用类型还是右值引用类型。

折叠规则:

下面x表示int、char等这些数据类型

x&、&、x& &&、x&& & 都被折叠为 x&

x&& && 折叠为 x&&

意思是,除了右值引用类型和右值引用类型折叠后为右值引用类型之外,其他类型之间的折叠都为左值引用类型。

右值转化规则:

形容像void f(T&& a);这种T&& 或者(auto&&)未定的引用类型,T为右值引用的模板参数,当实参为左值时,调用仍然成功,此时编译器推导T为左值引用。

比如:

template<typename T> void f(T&& a)

{

T b;

}

f(10); //10是一个(纯)右值

10是一个(纯)右值,根据右值转化规则,T被推导为int,展开f得到f(int &&),再根据折叠规则,此时相当于 void f(int&& a),a就被初始化为右值引用类型(a是一个左值)。

int x = 10; f(x); //x是一个左值

x是一个左值,根据右值转化规则,T被推导为 int&,展开f得到f(int& &&),再根据折叠规则,int& && -> int&,最后得到f(int& a),a就被初始化为左值引用类型(a是一个左值)。另外,由于T被推导为 int&,所以b 为左值引用类型,左值引用类型必须要在声明时初始化,所以编译器会在 T b; 这一行报错,编译不通过。

需要注意的是,仅仅是当发生自动类型推导(如函数模板的类型自动推导,或auto关键字)的时候,T&&才是 未定的引用类型。

比如:

template<typename T> void f(const T&& a);

这里和上面的相比,T&&前多了一个const,就不是未定的引用类型了,而是变成右值引用类型,自然a就被右值初始化了成为右值,这也体现C++的严谨。

利用右值引用后其变量可能是左值引用类型也可能是右值引用类型,依赖于初始化,并不是一下子就确定的这一特点,可以实现移动语义(移动构造)和完美转发(移动赋值)。

 

其他例子:

(1)左值和未定的引用类型的叠加

int&& var1 = 1; //var1是右值引用类型,val本身是左值

auto&& var2 = var1;

var1是左值,根据“右值转化”规则,auto被推导为int&,此时 int& && var2,再用引用折叠规则,得到 int& var2 ,所以var2被初始化为左值引用类型int&。

(2)

int a1, a2; //a1和a2是左值

auto&& b1 = a1; //b1被推导为左值引用类型,b1为左值

decltype(a1)&& b2 = a2;

a1的类型是int,则 decltype(a1)&& -> int&&,所以b2是右值引用类型int&&,a2的类型是int,把一个int 类型的变量赋值给int&& 类型的变量是不合法的,编译会报错。

可以通过std::move 把a2转化为右值引用类型。

decltype(a1)&& b2 = std::move(a2);

 

未完待续。。。。。

 

 

 

猜你喜欢

转载自www.cnblogs.com/xiaozhihong/p/9545047.html