1. 左值和右值

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

2. 左值引用

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

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

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

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

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

class A{
public:
A(){
cout << "construct..." << endl;
}
~A(){
cout << "Destruct..." << endl;
}
A(const A&a a){
cout << "Copy construct..." << endl;
}
private:
};
A GetA(){
return A();
}
int main(){
A a = GetA();
return 0;
}

以上代码,如果禁止编译器自动进行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本身的类型为右值引用,但它是一个具名的变量,为左值

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

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

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

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

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

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

    typedef const int T;
typedef T& TR;
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 编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值

    void print(int& i){
cout << "lvalue " << i << endl;
}
void print(int&& i){
cout << "rvalue " << i << endl;
}
void forward(int&& i){
print(i);
}
forward(10); //10为右值,进入forward之后,10变为i,i为一个变量,变为左值。
//因此,输出 "lvalue " << i << endl;

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

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

class A{
public:
A(): m_ptr(new int(0)){};
~A(){
delete m_ptr;
}
private:
int *m_ptr;
};
A Get(bool flag){
A a;
A b;
if (flag)
return a;
else
return b;
}
int main(){
A a = Get(false); //默认的拷贝构造函数,只是简单的将m_ptr进行赋值
//在 Get函数内部的b被析构的时候delete m_ptr, 当程序结束的时候a析构也delete m_ptr,二者m_ptr相同。造成内存的重复析构
return 0;
}

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

class A{
public:
A(): m_ptr(new int(0)){};
A(const A& a):m_ptr(new int(*a.m_ptr)){}; //深拷贝的拷贝构造函数
A(A&& a):m_ptr(a.m_ptr){ //移动构造函数
a.m_ptr = NULL;
} ~A(){
delete m_ptr;
}
private:
int *m_ptr;
};
A Get(bool flag){
A a;
A b;
if (flag)
return a;
else
return b;
}
int main(){
A a = Get(true); //调用移动构造函数
A b = a; //调用拷贝构造函数
return 0;
}

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

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

class A{
public:
A(): m_ptr(new int(0)){};
A(const A& a):m_ptr(new int(*a.m_ptr)){}; //深拷贝的拷贝构造函数
A(A&& a):m_ptr(a.m_ptr){ //移动构造函数
a.m_ptr = NULL;
}
A& operator=(const A& a){
m_ptr = new int(*a.m_ptr);
}
A& operator=(A&& a){
m_ptr = a.m_ptr;
a.m_ptr = NULL;
}
~A(){
delete m_ptr;
}
private:
int *m_ptr;
};
A Get(bool flag){
A a;
A b;
if (flag)
return a;
else
return b;
}
int main(){
A a = Get(true); //调用移动构造函数
A b = a; //调用拷贝构造函数 a = Get(false); //调用移动赋值操作符
a = b; //调用拷贝赋值操作符
return 0;
}

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

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

class A{
public:
  A(int x){
    x_ = x;   };
  A(const A& a){
    x_ = a.x_;
  }
A& operator= (const A& a){
    x_ = a.x_;
  }
private:
  int x_;
};
A  getA(int x){
  return A(x);
} ;
A a = getA(1); //拷贝构造函数
A b = getA(2); //拷贝构造函数 
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. 【C#】关闭 Window 之后,无法设置 Visibility,也无法调用 Show、ShowDialogor 或 WindowInteropHelper.EnsureHandle

    问题: 在做WPF项目时,点击一个按钮弹出一个自定义的窗体,然后点击X关闭该窗体,然后再点击按钮想弹出该窗体时,报错:关闭 Window 之后,无法设置 Visibility,也无法调用 Show.S ...

  2. signal(SIGPIPE, SIG_IGN) (转)

    signal(SIGPIPE, SIG_IGN) 当服务器close一个连接时,若client端接着发数据. 根据TCP 协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会 ...

  3. Quartz 与 Spring集成

    http://www.cnblogs.com/pigwing/archive/2011/07/12/2104002.html http://blog.arganzheng.me/posts/quart ...

  4. sysctl -P 报错解决办法

    sysctl -P 报错解决办法问题症状修改 linux 内核文件 #vi /etc/sysctl.conf后执行sysctl  -P 报错error: "net.bridge.bridge ...

  5. iOS边练边学--应用数据存储的常用方式(plist,Preference,NSKeyedArchiver)其中的三种

    iOS应用数据存储的常用方式: XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档(NSCoding) SQLite3--这里暂且不讲 Core D ...

  6. windows server 2003中用系统自带工具调整磁盘分区大小

    先在需要扩展的右边留出未分配的磁盘空间,可以通过 我的电脑 右键 管理 磁盘管理来操作 首先 进入cmd界面 然后输入Diskpart 这个时候进入DISKPART> 界面 然后你 先选择磁盘一 ...

  7. android 创建 xml文件

    android创建xml文件的方法. 要操作android的外部存储,所以要在AndroidManifest.xml文件中添加权限. <uses-permission android:name= ...

  8. 时间操作(struct tm、time_t)求指定日期 前n天的日期

    1.在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中的定义如下: #ifndef _TM_DEFINED struct tm { int tm_sec; /* 秒–取值 ...

  9. C API 连接MySQL及批量插入

    CMySQLMgr.h: #ifndef _CMYSQLMGR_H_ #define _CMYSQLMGR_H_ #include <iostream> #include "my ...

  10. 用X264编码以后的H264数据

    输入的数据准备好了,编码后的数据都在x264_nal_t的数组.我这里设置的参数是Baseline Profile,所以编码后没有B帧,将编码后的数据保存分析后发现,第一次编码的时候会有4个NAl,分 ...