再次来写左值右值相关的东西个人心里是十分惴惴不安的,一来这些相关的概念十分很差理解,二来网上相关的文章实在太多了,多少人一看这类题目便大摇其头,三来也怕说不清反而误导了别人,反复纠缠这些彷佛无关大雅的语言细节实在也有成为 language lawyer 之嫌。但我仍是决定再总结一次,由于这是我一直以来学习新东西的一种方式,只有把学到的东西真正写清楚说明白了才是真的理解了,再者也但愿本身的经验总结能帮助到有一样困惑的人。html
咱们一直说左值右值,从 c++ 的术语角度来看,这其实并不十分准确,确切地说应该是左值表达式,右值表达式:表达式是有值的,值是有类型的,值是动态的,类型是静态的,这是基本的概念。而咱们说的左值右值,是对值的一种分类,这两个称呼也是从 c 时代遗传下来的:左值是指能出如今等号左边的值,右值是指只能出如今等号右边的值。简单来讲,左值就是咱们平时定义的变量,右值就是一些临时变量。但到了 c++11,这个分类被细化扩充了,以下所示[N3690,3.10.1]:
c++
所以:算法
一个表达式要么是一个 glvalue(generalized lvalue),要么是一个 rvalue;一个 glvalue 要么是一个 lvalue,要么是一个 xvalue(expiring value);一个 rvalue 要么是一个 xvalue,要么是一个 prvalue (pure rvalue)。函数
乍一看好像状况变得好复杂,其实不是,图中所说 lvalue 与 c 时代的 lvalue 几乎表达一个意思(不妨称为纯左值),prvalue 与 c 时代的 rvalue 也几乎表达一个意思,所谓纯右值 (pure rvalue),只是多了一个 xvalue,一个介于纯左传与纯右值之间的奇怪物种。本质上来讲,xvalue 是 c 时代的 lvalue,它不是中间变量临时变量之类的没有名字的纯右值,之因此再创造出这样一个新的值类型是由于有些时候,咱们但愿可以将一个纯左值当成临时变量(纯右值)同样来使用,这种被当成纯右值来使用的左值就是 xvalue,提及来很绕,本质上 xvalue 就是一些从程序逻辑上看要 "过期" 的变量(expiring value),它的名字也正是取义自这里。学习
xvalue 只能经过两种方式来得到,这两种方式都涉及到将一个左值赋给(转化为)一个右值引用[N3690,3.10.1]:指针
static_cast<T&&>(t);
该表达式获得一个 xvalue。T&& fun() { return t; };
, 则调用 fun() 时, 返回一个 xvalue。对于第 2 点,有一个与之相似的写法值得你们注意:若是一个函数的返回类型是左值引用,那么调用这个函数获得的返回值将是一个 lvalue[N3690,3.10.1],之因此特别地说这个事,是由于若是一个函数的返回值不是引用类型,那调用这个函数获得的结果将是一个临时变量,是个右值,并且是纯右值(prvalue),嗯,不要搞昏了。c++11
这是另外两个容易混为一谈的概念,引用在 c++ 里是一个很特别的东西,就个人理解,确切地说引用应是一种类型,和 int, float 等相似,好比说 T& t = v;
, 则 t 是一个变量,它的类型是引用,指向一个左值,所以也称为左值引用,因此 t 自己是一个左值,类型是一个左值引用。与此相对应,咱们也有右值引用: T&& t2 = fun();
,一样的 t2 是一个左值,但它的类型是右值引用。因此,咱们平时说的"引用"确切来讲应该称做"引用变量"才准确,与"整型变量","字符变量"相对(N3690, 8.5.3.1)。固然引用变量这个说法比较牵强(甚至有些政治不正确),毕竟它和通常变量相比实在太不类似了,好比它通常不占内存,好比对它取地址,获得的不是变量自己的地址,好比定义时必须初始化,且不能再次被赋值等等特殊之处,怎么看都是异类。不少人倾向于把引用与指针并论来理解,它们其实也不大同样,虽然实现上基本能够认为引用是一个由编译器自动帮你解引用(deference)的指针。code
须要注意的是,左值引用变量只能用左值(lvalue)来初始化,右值引用变量只能用右值(xvalue 及 prvalue)来初始化,惟一的例外是 const 类型的左值引用,它也能用右值来初始化。值得注意的是,引用对临时变量的生命周期是有影响的,如前面所说,临时变量是右值,当一个临时变量被一个引用(const 左值引用或右值引用)指向时,它的生命周期会被延长[N3690,12.2.5]。htm
关于右值引用,还有一个很容易让人迷惑的语义须要说一说,写法上定义一个右值引用变量的语法如右所示:some_type&& rv_ref = some_rvalue;
,但这里要求 somt_type 必须是一个具体的完整的类型,而不能是模板参数,auto,或 decltype 等须要推导的类型,若是 T 是一个须要推导的类型,则 T&& u_t_ref
称为 universal reference 或 forwarding reference,根据 reference collapsing 原则及右值引用推导原则,u_t_ref
最后既多是左值引用也多是右值引用,具体能够参看这里。blog
接下来是不少人特别关心的 std::move()
与 std::forward()
,对这俩偏偏想总结的却很少,总的来讲,以个人浅见,std::move()
的主要做用是将一个左值转为 xvalue, 它的实现,本质上就是一个 static_cast<>
。而 std::forward()
则是用来配合 forwarding reference 实现完美转发,它主要的做用是将一个类型为引用(左值引用或右值引用)的左值,转化为它的类型所对应的值类型,这个说法实在是太没法理解太不知所云了哈哈,因此我放弃用本身的话来解释了,请参看 cppreference 上的例子。
move 语义是一个你们必定要注意,接受,掌握,并理解好的东西。过去使用 c++ 98/03 咱们常说 rule of three,便是:若是一个类定义了析构函数,拷贝构造函数,赋值构造函数之一,那么这三个函数都应该要明肯定义,目的是为了确保该类的拷贝语义被正确地处理。
到了 c++11,这个 rule of three 得改为 rule of five。咱们知道,一个类若是定义了拷贝构造函数和赋值构造函数,则咱们称它为 copyable 的类。同理,若是一个类定义了 move constructor 和 move assignment operator,那么咱们称它为 movable 的类。从使用上来讲,右值引用是一个很 tricky 的东西,它的正确使用场合应该只有两个:一个是为自定义类型实现 move 语义,一个是配合 forwarding reference 来实现完美转发。当你考虑写一个以右值引用做为参数类型的通常函数时,每每这是错误的开始,正确的做法是为相应的类型定义 move 语义,具备 move 语义的类型在做为参数传递时,要么直接传值(sink parameter),要么传 const 左值引用(read only),根本不须要右值引用这种 tricky 的东西。
所以,可以在适当时候为自定义类型实现 move 语义是一个基本素质,就正如之前处理 copy 语义同样(会不会将一个类继承自 boost::noncopyable 也是基本素质)。STL 中全部的容器算法都妥善定义了对适用类型 move 语义的要求,如只适用于 copyable,或只适用于 movable 等,容器自己更都是 movable 的。通常来讲,move 是一个更轻量的操做,对容器其实更友好(内部 copy 能够改成 move,效率更高),好比 vector<>,之前要求其所保存类型必须 copyable,如今 c++11 之后,只 movable(且 noexcept)的类型也被容许了(固然此时用户就不能调用那些须要 copy 的操做了)。因此,明肯定义好自定义类型的 move 语义,意义是很大的。