1. 左值和右值

左值是表达式结束之后仍然存在的持久化对象,而右值是指表达式结束时就不再存在的临时对象。 
    c++11中,右值分为两种类型:将亡值(xvalue, expiring value),另一个是纯右值(prvalue, pure rvalue). 非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值;将亡值是c++11新增的、与右值引用相关的表达式,比如,将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值。

2. 左值引用

左值引用是对左值进行引用的类型,分为常量左值引用和非常量左值引用。其中,常量左值引用可以引用常量左值、非常量左值、常量右值、非常量右值;而非常量左值引用只能引用非常量左值

  1. int x = 1;
  2. const int y = 2;
  3. int& p1 = x;
  4. int& p2 = y; //出错,非常量左值引用无法引用常量
  5. const int& p3 = x;
  6. const int& p4 = y;
3. 右值引用

c++11增加了右值引用类型,实现对一个右值进行引用,标记为T&&. 因为右值不具名,因此只能通过引用的方式找到它。

右值引用延长右值的生命期 
    左值引用和右值引用必须在声明的时候立即初始化,因为引用本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用的声明,该右值又“重获新生”,其生命周期变得和右值引用类型变量的生命期一样长,只要该变量还活着,该右值临时量将会一直存活下去。

利用右值引用延长右值生命期,可以避免一些临时对象的构造和析构,从而提高性能。比如:

  1. class A{
  2. public:
  3. A(){
  4. cout << "construct..." << endl;
  5. }
  6. ~A(){
  7. cout << "Destruct..." << endl;
  8. }
  9. A(const A&a a){
  10. cout << "Copy construct..." << endl;
  11. }
  12. private:
  13. };
  14. A GetA(){
  15. return A();
  16. }
  17. int main(){
  18. A a = GetA();
  19. return 0;
  20. }

以上代码,如果禁止编译器自动进行RVO优化,完全尊造c++的语法规则,则程序的输出为 

其中,GetA()函数内部的A()函数生成一个内部的对象obj1时调用 构造函数; 在函数返回时,临时对象obj2通过拷贝构造函数拷贝了该内部对象的内容;函数返回之后,内部对象obj1调用析构函数销毁;然后 A a = obj2,通过调用拷贝构造函数,a拷贝obj2;a赋值结束之后,obj2 调用析构函数销毁;最后程序结束时,a调用析构函数销毁。

这整个过程中,在调用函数GetA时会构造和析构临时对象obj2,因此造成不必要的浪费。在c++98/03中可以通过const A& a = GetA()来将临时对象obj2赋值给一个常量左值引用a,延长了该临时对象的生命期;而在c++11中,也可以通过右值引用来延长函数返回时的临时对象(右值)的生命期,而不是在a = GetA()一结束就销毁。 

其中,GetA()函数内部的A()函数生成一个内部的对象obj1时调用 构造函数; 在函数返回时,临时对象obj2通过拷贝构造函数拷贝了该内部对象的内容;函数返回之后,内部对象obj1调用析构函数销毁;然后 const A& a = obj2或者 A&& a = obj2,此时都是对引用进行初始化,没有对象的构造和析构;当程序结束时,obj2(也就是a)进行析构。

4. T&&的赋值

(1)左值和右值是独立于他们的类别的,右值引用类型可能是左值也可能是右值 
(2)auto&& 或者函数参数类型自动推导的T&&是一个未定的引用类型,被称为universal reference,它可能是左值引用类型也可能是右值引用类型,取决于初始化的值类型。 
(3)所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用叠加都为左值引用。当T&&为模板参数时,输入左值,它将会变成左值引用,而输入右值时则变为具名的右值引用。 
(4)编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值。

4.1 左值和右值是独立于他们的类别的,右值引用类型可能是左值也可能是右值 
    int && a = xxxx;中a本身的类型为右值引用,但它是一个具名的变量,为左值

  1. int &&var1 = 10; //var1为右值引用
  2. auto&& var2 = var1; //var2此时为一个universial reference,但是由于var1本身是一个左值,因此var2 为左值引用
  3. int w1, w2;
  4. auto&& v1 = w1; //左值引用
  5. decltype(w1)&& v2 = w2; //int &&v2 = w2; //此时对一个右值引用初始化为一个左值,出错!!

4.2 auto&& 或者函数参数类型自动推导的T&&是一个未定的引用类型,被称为universal reference, 
它可能是左值引用类型也可能是右值引用类型,取决于初始化的值类型。

  1. auto&& a = 10; //a直接由一个右值初始化,则a为一个右值引用类型
  2. int x = 20;
  3. auto&& b = x; //b由一个左值进行初始化,则b为一个左值引用类型
  4. template<typename T>
  5. void func(T&& a){
  6. cout << a << endl;
  7. }
  8. ....
  9. func(10); //被一个右值初始化,a为右值引用类型, a类型为 int&&
  10. int x = 10;
  11. func(x); //左值引用类型, a为int& !!!!!
  12. void f(std:vector<T>&& param); //这里需要注意,这里既有推导类型T,又有确定类型vector。。。。在实际
  13. //应用时,调用该函数之前,Vector<T>中的推断类型T已经确定了,所以到调用该f函数的时候就没有类型推
  14. //则 param为右值引用
  15. template<typenmae T>
  16. void f(const T&& param){ //这里虽然有类型推导,但是由于带有const限定,则仍然为右值引用
  17. }

即右值操作符&&只在 auto && / T&& ,且不带cv限定符的时候,才需要推断具体为左值还是右值引用,否则一律为右值引用。

4.3 所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用叠加都为左值引用。 
当T&&为模板参数时,输入左值,它将会变成左值引用,而输入右值时则变为具名的右值引用。

引用折叠 
    由于存在T&&这种未定引用类型,当它作为参数时,有可能被一个左值引用或者右值引用的参数初始化,这时经过类型推导的T&&类型,相比右值引用(&&)会发生类型的变化,这种变化成为引用折叠。

  1. typedef const int T;
  2. typedef T& TR;
  3. TR v
TR的定义 v的定义 v的实际类型
T& TR v T&
T& TR& v T&
T& TR&& v T&
T&& TR v T&&
T&& TR& v T&
T&& TR&& v T&&

从而,可以看出 所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用叠加都为左值引用。

4.4 编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值

  1. void print(int& i){
  2. cout << "lvalue " << i << endl;
  3. }
  4. void print(int&& i){
  5. cout << "rvalue " << i << endl;
  6. }
  7. void forward(int&& i){
  8. print(i);
  9. }
  10. forward(10); //10为右值,进入forward之后,10变为i,i为一个变量,变为左值。
  11. //因此,输出 "lvalue " << i << endl;

5. 右值引用优化性能,避免深拷贝

对于含有堆内存的类,需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,将会导致堆内存的重复删除。

  1. class A{
  2. public:
  3. A(): m_ptr(new int(0)){};
  4. ~A(){
  5. delete m_ptr;
  6. }
  7. private:
  8. int *m_ptr;
  9. };
  10. A Get(bool flag){
  11. A a;
  12. A b;
  13. if (flag)
  14. return a;
  15. else
  16. return b;
  17. }
  18. int main(){
  19. A a = Get(false); //默认的拷贝构造函数,只是简单的将m_ptr进行赋值
  20. //在 Get函数内部的b被析构的时候delete m_ptr, 当程序结束的时候a析构也delete m_ptr,二者m_ptr相同。造成内存的重复析构
  21. return 0;
  22. }

而如果为类的拷贝构造函数提供了深拷贝,则在程序产生临时对象的时候会出现大量的内存拷贝,降低性能。此时可以使用移动构造函数进行改进。

  1. class A{
  2. public:
  3. A(): m_ptr(new int(0)){};
  4. A(const A& a):m_ptr(new int(*a.m_ptr)){}; //深拷贝的拷贝构造函数
  5. A(A&& a):m_ptr(a.m_ptr){ //移动构造函数
  6. a.m_ptr = NULL;
  7. }
  8.  
  9. ~A(){
  10. delete m_ptr;
  11. }
  12. private:
  13. int *m_ptr;
  14. };
  15. A Get(bool flag){
  16. A a;
  17. A b;
  18. if (flag)
  19. return a;
  20. else
  21. return b;
  22. }
  23. int main(){
  24. A a = Get(true); //调用移动构造函数
  25. A b = a; //调用拷贝构造函数
  26. return 0;
  27. }

使用移动构造函数,其参数是一个右值引用类型的参数 A&&, 没有深拷贝,只有浅拷贝,避免了对临时对象的深拷贝,提高了性能。这里的A&&用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数;否则,则会选择拷贝构造函数。 
    在拷贝的源对象为临时的时候,调用移动构造函数,该函数将原来临时对象的资源移动到了拷贝的目的对象,并且将源对象的资源赋空。则之后,源对象被析构,资源已经被转移到目的对象。

除了使用移动构造补充拷贝构造,还可以使用移动赋值操作符代替拷贝赋值操作符。

  1. class A{
  2. public:
  3. A(): m_ptr(new int(0)){};
  4. A(const A& a):m_ptr(new int(*a.m_ptr)){}; //深拷贝的拷贝构造函数
  5. A(A&& a):m_ptr(a.m_ptr){ //移动构造函数
  6. a.m_ptr = NULL;
  7. }
  8. A& operator=(const A& a){
  9. m_ptr = new int(*a.m_ptr);
  10. }
  11. A& operator=(A&& a){
  12. m_ptr = a.m_ptr;
  13. a.m_ptr = NULL;
  14. }
  15. ~A(){
  16. delete m_ptr;
  17. }
  18. private:
  19. int *m_ptr;
  20. };
  21. A Get(bool flag){
  22. A a;
  23. A b;
  24. if (flag)
  25. return a;
  26. else
  27. return b;
  28. }
  29. int main(){
  30. A a = Get(true); //调用移动构造函数
  31. A b = a; //调用拷贝构造函数
  32.  
  33. a = Get(false); //调用移动赋值操作符
  34. a = b; //调用拷贝赋值操作符
  35. return 0;
  36. }

上面添加了move版本的构造函数和赋值函数,对原来的类产生了一些影响: 如果提供了move版本的构造函数,则不会生成默认的构造函数。另外,编译器永远不会自动生成move版本的构造函数和赋值函数,他们需要手动显式的添加。 
    当添加了move版本的构造函数和赋值函数的重载形式后,某一个函数调用应当使用哪一个重载版本呢?下面是按照判决的优先级列出的3条规则: 
(1)常量值只能绑定到常量引用上,不能绑定到非常量引用上 
(2)左值优先绑定到左值引用上,右值优先绑定到右值引用上 
(3)非常量值优先绑定到非常量引用上

c++类的拷贝构造函数和赋值操作符:

  1. class A{
  2. public:
  3.   A(int x){
  4.     x_ = x;
  5.  
  6.   };
  7.   A(const A& a){
  8.     x_ = a.x_;
  9.   }
  10. A& operator= (const A& a){
  11.     x_ = a.x_;
  12.   }
  13. private:
  14.   int x_;
  15. };
  16. A  getA(int x){
  17.   return A(x);
  18. } ;
  19. A a = getA(1); //拷贝构造函数
  20. A b = getA(2); //拷贝构造函数 
  21. b = a;    //赋值操作符

拷贝构造函数是在构造类的对象的时候调用的,即 A a = getA(1); 这句新建了一个类A的对象a,调用拷贝构造函数。而 赋值操作符是对一个已经存在的对象进行重新赋值, 不重新生成对象。

参考

http://www.cnblogs.com/hujian/archive/2012/02/13/2348621.html

+

c++11——右值引用的更多相关文章

  1. C++11右值引用

    [C++11右值引用] 1.什么是左值?什么是右值? 左值是表达式结束后依然存在的对象:右值是表达式结束时就不再存在的对象. 2.std::move的作用是什么? std::move用于把任意类型转化 ...

  2. 关于C++11右值引用和移动语义的探究

    关于C++11右值引用和移动语义的探究

  3. C++ 11 右值引用

    C++11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习“移动语义”(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 注意:左值右值翻译可能有些问题 *L ...

  4. C++ 11 右值引用以及std::move

    转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...

  5. 【转】C++ 11 右值引用的理解

    右值引用的目的之一,是为了C++中一个比较影响性能的问题:拷贝临时对象,例如,在 int foo(){ ... } int x; x = foo(); 中,在第三句中,发生了以下的事情: 1.销毁 x ...

  6. C++11右值引用和std::move语句实例解析

    关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践 ...

  7. C++11 右值引用和转移语义

    新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...

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

    新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...

  9. c++11 右值引用和移动语义

    什么是左值.右值 最常见的误解: 等号左边的就是左值,等号右边的就是右值 左值和右值都是针对表达式而言的, 左值是指表达式结束后依然存在的持久对象 右值是指表达式结束时就不再存在的临时对象区分: 能对 ...

随机推荐

  1. EFM32 DMA/PRS例程

    /**************************************************************************//**  * @file  * @brief H ...

  2. 百兆千兆网口100Base-TX/1000Base-T

    100Base-TX快速以太网目前制定的三种有关传输介质的标准之一. 另外两种是100Base-T4,100Base-FX. 100标识传输速率为100Mbit/s. base标识采用基带传输. T代 ...

  3. Mac为nginx安装nginx-sticky-module

    Mac为nginx安装nginx-sticky-module nginx版本: nginx-1.9.8 nginx-sticky-module版本:nginx-sticky-module-ng *注意 ...

  4. C++实现 逆波兰表达式计算问题

    C++实现 逆波兰表达式计算问题 #include <iostream> #include <string> using namespace std; class Stack ...

  5. Mysql 常用工具

    mysqladmin:用于管理MySQL服务器的客户端 mysqladmin是一个执行管理操作的客户程序.可以用它来检查服务器的配置和当 前的状态,创建并删除数据库等等. 这样调用mysqladmin ...

  6. [oracle] 如何使用myBatis在数据库中插入数据并返回主键

    在MyBatis中,希望在Oracle中插入数据的同时返回主键值,而非插入的条数. ① oracle使用 selectKey. U_USER_INFO_SEQ 是在数据库中定义好的这张表关联的序列se ...

  7. 各个层次的gcc警告

    http://blog.csdn.net/lizzywu/article/details/9419145 各个层次的gcc警告从上到下覆盖 变量(代码)级:指定某个变量警告 int a __attri ...

  8. 编译 & 执行 C++ 程序

    编译 & 执行 C++ 程序接下来让我们看看如何把源代码保存在一个文件中,以及如何编译并运行它.下面是简单的步骤: 打开一个文本编辑器,添加上述代码.保存文件为 hello.cpp.打开命令提 ...

  9. Nodejs入门手记 (01):Hello World的WEB程序

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Allong,谢谢! “滚滚长江东逝水,浪花淘尽英雄.是非成败转头空.” - <临江仙·杨慎·明> 很熟悉的旋律,鸡汤了一下:高考是 ...

  10. JavaScript 学习笔记(三)

    本章学习内容: 1.数组的使用 2.类和对象细节. 3.this关键字的使用 4.构造函数,成员函数的使用 1.数组的使用   在任何的语言中,必须要有的就是数组了,有了数组,使得很多操作都变得非常的 ...