表达式

表达式子由operatoroperand组成。

表达式可以求值,其求值的结果有2个属性,有typevalue categories

type是值表达式类型,是函数?定义?比较?运算?等等。

categories来说,c++11前,只有左值(lvalue)和右值(rvalue); c++11后,任何value categories(值类型)都是下面的三种之一:

lvalue xvalue prvalue

gvalue为广义左值, 包括lvaluexvalue,类似传统的lvalue; rvalue为右值, 包括xvalueprvalue,类似传统的rvalue; 可见xvalue可以是左值也可以是右值,下面详细会讲。

左值(lvalue)

没有准确定义,但通常下,能够用&取地址的表达式是左值,有名字的变量也一定是左值。

//变量
`int a;`

//赋值运算符的左侧必须是左值,整个表达式也是一个左值;
`a = 10;`
//复合赋值
`a += 10;`
//复杂点
`((b+=10)=10)+=10;`

//&取地址必须作用于左值,即能够被&符号使用的都是左值。
`int* c= &a; `

//解引用是左值,允许出现在赋值的左边;
`*c=100;` 

// 下标运算
`c[0] = 100; `

//迭代器解引用同上
`vector<int>::iterator it;
*it  = 100; `

//左目自增为左值,先+后用,所以为左值
`++a; 
--*it; 
++a=10;`

//`int*p`表达式是是左值,而decltype作用于左值的结果得到一个引用类型,即int &
` int *p; decltype(*p) `

//返回T& 的函数是一种表达式其返回的结果为左值,允许
`
T t1;
T& func(..); 
func() = t1; `

纯右值(prvalue)

纯右值是传统右值的一部分,纯右值是表达式产生的中间值,不能取地址。 纯右值一定会在表达式结束后被销毁。

一般满足下列条件:

1)本身就是赤裸裸的、纯粹的字面值,如3、false;

2)求值结果相当于字面值或是一个不具名的临时对象。

//字面值
`10`

//运算符(`+-*/`)的结果
`int a,b,c; a=c+b  c=c*d `

//比较运算符号的结果
`int a,b,c; (a==b) (b>c)`

//右目自增 先增后运算,所以是右值
`*it++ ; a++ ;`
//不允许
`a++ =100;`

//返回值函数的返回结果为右值
`int func(){return int a}; `
//错误,普通函数的返回func()是右值
`int* c = &func();`
//允许, 可以取函数地址
`int* c = (int*)&func;`

//取地址后为右值
`
int a = 10;
int *d = &a;`

消亡值(xvalue)

http://blog.csdn.net/zwvista/article/details/12306283

xvalue是c++11的不具名的右值引用引入的。 以下情况的表达式求值结果为xvalue,准确的说叫不具名右值引用:

返回T&& 的函数或者运算重载;
static_cast<char&&>(x).
std::move(x).

这种属于新的”右值”,由右值引用带来,通常用来完成移动构造移动赋值的特殊任务,这类右值具备一个新的名字,叫将亡值

A a = b; //发生拷贝赋值
//std::move仅是编译器的戏法,本质上它什么也没干.
A a = std::move(b); //如果a实现了移动赋值,则使用移动赋值,不发生拷贝.

事实上,将亡值不过是C++11提出的一块晦涩的语法糖。它与纯右值在功能上及其相似,如都不能做操作符的左操作数,都可以使用移动构造函数和移动赋值运算符。当一个纯右值来完成移动构造或移动赋值任务时,其实它也具有“将亡”的特点。一般,

我们不必刻意区分一个右值到底是纯右值还是将亡值。

下面再看看到底什么是右值引用。

右值引用

经由T&&这一语法形式所产生的引用类型都叫做右值引用,通过右值引用的声明,右值又“重获新生”,不会消亡。这种广义的右值引用又可分为以下三种用法:

  • 不具名右值引用 (即上面讲的消亡值)
  • 具名右值引用(表达式结果为lvalue)
  • 转发引用(std::forward() 保持原来的类型)

转发引用(万能引用)

特别注意,当发生自动类型推导(如函数模板的类型自动推导,或auto关键字)的时候,T&&为universal references(万能引用)。如果被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值,它是左值还是右值取决于它的初始化。

再次提醒是:

模板参数推导(T &&)和(auto &&)这两种场景,和const &还是不一样,也叫farward reference.

auto &&const &的最大区别: 后者是const,意味着不能修改值,而前者求值本身是左值,可以修改值. https://stackoverflow.com/questions/13230480/what-does-auto-tell-us

// auto && universal ref的例子, 取决于传什么
struct X {};  
// var1是具名右值引用,只能绑定右值,其求值结果是左值
X&& var1 = X();                          
// var2是具名右值引用,不能绑定左值
//X&& var2 = var1;                          

// var3是universal ref,可以绑定左值
auto&& var3 = var1;                          
// var4是universal ref,可以绑定右值X()
auto&& var4 = X();                         

// T&& universal ref的例子, 取决于传什么
template<typename T> void f(T&& t);
//t是右值
f(10);
//t是左值 
int x = 10;
f(x); 

//纯右值引用的例子
template<typename T>
class Test {
    Test(Test&& rhs);  //纯右值引用 转移构造函数 不是universal ref
};

std::move

关于std::move,move实际上它并不能移动任何东西,它唯一的功能:

将一个左值强制转换为一个右值。

称为右值后意味着,不能再使用原来的变量了.

//作用域外,t将消亡,通过move续命
T&& t1;
{
    T t;
    t1  = std::move(t); //t1为右值引用 
}
T & t2 = std::move(t); //报错 左值引用仅接受左值

T t2  = std::move(t); //移动语义 ,将调用转移拷贝构造函数

一个例子

最大的共享,最小的拷贝,move用来避免一切可能的拷贝构造和赋值。假设Demo有移动构造:

shared_ptr<vector<shared_ptr<Demo>>>  vector_sh_ptr_test()
{
	vector<shared_ptr<Demo>> vec;
	vec.push_back(make_shared<Demo>("demo 11111")); 

	Demo demo("demo11111");
    //move(demo) 避免拷贝构造,调用移动构造。
    vec.push_back(make_shared<Demo>(std::move(demo))); 


	cout << "3333\n";
	return make_shared<vector<shared_ptr<Demo>>>(std::move(vec));
    // move(vec) 避免在调用一次vec的构造,而是调用移动构造。
}

auto vec = vector_sh_ptr_test();
(*vec)[0]->show(); 

std::forward

C++11之前调用模板函数时,存在一个比较头疼的问题,即如何正确的传递参数,为此需要定义很多额外的泛型 :

template <typename T>
void forwardValue(T& val)
{
    processValue(val); //右值参数会变成左值 
}
template <typename T>
void forwardValue(const T& val)
{
    processValue(val); //参数都变成常量左值引用了 
}

有了forward后的解决方案:

template <typename T>
void forwardValue(const T&& val)
{
    processValue(std::forward<T>(val); //随val而变 ,方便
}

引用折叠规则:

所有的右值引用叠加到右值引用上仍然还是一个右值引用; 所有的其他引用类型之间的叠加都将变成左值引用。

转移构造与拷贝

在C++11及之前的版本中,我们用左值去初始化一个对象或为一个已有对象赋值时,会调用拷贝构造拷贝赋值函数:

c++11以后,当使用右值(rvalue)来初始化或者赋值时,经过编译器的右值语义分析,将调用转移构造转移赋值函数.

T a ,b;
a = b;
T c(a); // 普通拷贝构造
T d(std::move(a)) //转移拷贝构造

左值引用(也就是&)

左值引用与右值引用都是引用,引用都必须初始化。 1 常规左值引用只能接受左值,而且必须初始化。 int b = 0 ;int & a = b; http://blog.csdn.net/xiao__tian__/article/details/51814617

2 const左值应用是万能的,可以接受const左值,左值,右值,const右值 const int & a = 100; const int & c = func();

参考理解

lvalue rvalue的理解

value_category

C++ lvalue,prvalue,xvalue,glvalue和rvalue详解(from cppreference)

话说C++中的左值、纯右值、将亡值

C++11 右值引用与转移语义

从4行代码看右值引用