左值与右值
参见:http://www.cnblogs.com/hujian/archive/2012/02/13/2348621.html
1: int a = 0;
2: int* b = &(a++);
3:
4:
上面的代码,编译不通过:
error C2102: '&' requires l-value
提示说a++不是左值对象(l-value),即右值对象(r-value)。
1: int a = 0;
2: int* b = &(++a);
3:
编译通过,表明++a是左值对象。
1: //int a = 0;
2: 5CB0 mov dword ptr [ebp-18h],0
3: //int* b = &(++a);
4: 5CB7 mov eax,dword ptr [ebp-18h]
5: 5CBA add eax,1
6: 5CBD mov dword ptr [ebp-18h],eax
7: 5CC0 lea ecx,[ebp-18h]
8: 5CC3 mov dword ptr [ebp-24h],ecx
1: //int a = 0;
2: 5CB0 mov dword ptr [ebp-18h],0
3: //int b = (a++);
4: 5CB7 mov eax,dword ptr [ebp-18h]
5: 5CBA mov dword ptr [ebp-24h],eax
6: 5CBD mov ecx,dword ptr [ebp-18h]
7: 5CC0 add ecx,1
8: 5CC3 mov dword ptr [ebp-18h],ecx
对于右值对象,可以认为它存在于寄存器中,存在于CPU执行某条表达式的过程中,当执行完这条表达式后,已经没有内存可以保存右值对象了,因此右值对象被销毁了。
除非,将该右值对象赋值给了另外一个对象。
但是,如果把一个右值对象赋值给一个左值对象,那么其实是发生了一次深拷贝,分配了一个左值对象,将右值对象深拷贝给左值对象,然后释放这个右值对象。
这个过程,有一次多余的“分配、深拷贝、释放”流程,因为反正右值对象在表达式结束之后也不会被其他地方引用到了,不如把右值对象里面包含的深层次内容(需要深拷贝的那部分内容)直接赠予左值对象,然后简单地释放掉右值对象剩余的空架子。
这但是C++11中提出的右值引用要解决的问题。
通过T &可以定义一个左值引用,通过T &&可以定义一个右值引用。
C++11通过添加了T &&这个标记,代表编译器能够将它与类型T &识别出来,这样就可以支持函数重载,以区别对待左值引用和右值引用。对于左值引用,与原来的一样,如果要拷贝对象时,要深度拷贝;而对于右值引用,因为右值在表达式结束后,对象马上就不存在了,所以可以通过浅拷贝,配合盗取右值对象的深层次内容指针的方式,完成深层次内容的移动操作,即move操作,从而提高性能。常用方法是,为需要经常拷贝的大对象同时添加copy和move模式的constructor和assignment operator。
1: class T
2: {
3: public:
4:
5: T(T& t); // copy constructor
6: T(T&& t); // move constructor
7:
8: T& operator=(T& t); // copy assignment
9: T& operator=(T&& t); // move assignment
10: }
这样,当对于T对象的引用b构造另一个T对象a时,或者将一个T对象的引用b赋值给另一个T对象a时,编译器会自动区分对象b是左值引用,还是右值引用,如果是左值引用,就使用copy constructor/copy assignment;如果是右值引用,就使用move constructor/move assignment。
左值和右值都是针对表达式而言的,
左值是指表达式结束后依然存在的持久对象,
右值是指表达式结束时就不再存在的临时对象。
一个区分左值与右值的便捷方法是:看能不能对表达式取地址,
如果能,则为左值,
否则为右值。
非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值。
如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。
常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。
右值(http://blog.csdn.net/zentropy/article/details/6973411)
右值是指创建匿名临时对象的表达式。右值的名字来自这样一个事实,内置类型的右值表达式只能出现在赋值操作符的右侧。这一点和左值不同,不带 const 的时候,左值是可以出现在赋值操作符的左侧的,右值表达式生成的对象没有任何持久的标识用来向它赋值。
不过,我们要讨论的是匿名临时对象的另一个重要特性,就是它们可以在表达式中只使用一次。你怎么可能再一次提及这样的一个对象呢?它没有名字(即“匿名”);而且在整个表达式求值完毕后,对象即被销毁(即“临时”)!
如果你知道你是从一个右值进行复制的话,你就有可能从源对象处将复制开销较高的资源“偷过来”,在目标对象中使用它们而不会有任何人留意它。在前面的例子中,就是将源 vector 中动态分配的字符串数组的所有权传递给目标 vector。如果我们可以在某种程度上让编译器来为我们执行这种“转移”操作,那么从以传值方式返回的 vector 来初始化 names 的代价就非常低——几乎为零。
以上是关于第二次复制的,那么第一次复制呢?原则上,当 get_names 返回时,必须将函数的返回值从函数的内部复制到外部。很好,返回值具有与匿名临时对象一样的特性:它们马上就会被销毁,以后也不会再被用到。所以,我们可以用相同的方法来消除掉第一次复制,将资源从函数内部的返回值处转移给函数调用者可见的匿名临时对象。