C++ 左值右值
表达式
表达式子由operator
和operand
组成。
表达式可以求值,其求值的结果有2个属性,有type
和value categories
。
type是值表达式类型,是函数?定义?比较?运算?等等。
categories来说,c++11前,只有左值(lvalue)和右值(rvalue); c++11后,任何value categories(值类型)都是下面的三种之一:
lvalue xvalue prvalue
gvalue为广义左值, 包括lvalue
和xvalue
,类似传统的lvalue;
rvalue为右值, 包括xvalue
和prvalue
,类似传统的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();
参考理解
C++ lvalue,prvalue,xvalue,glvalue和rvalue详解(from cppreference)