C++11学习笔记之4——右值引用(rvalue reference)和移动语义(Move Semantics) 本文所谈到的两个标准在VS2012中已经可以实现
1、右值引用(rvalue reference) 左值是可以获取到地址的一个量,具有变量名便可以作为左值来引用(例如int count = 10;),通常出现在赋值符号左边. 而右值引用是C++11引入的一个新概念,用来引用那些会自动销毁的临时对象,主要目的是对这些临时对象提供移动复制构造函数和移动operator=(稍后解释),但是如果深入研究或许还会有更多新的用途被发掘。 在C++中,下面的代码是不合法的,显而易见:
而如果在C++11中,可以通过&&(注意不是“与”操作)符号来声明一个右值引用,它会去调用默认的移动构造函数(或者移动operator=).
这样写一般没有多大意义,更多的意义是在右值引用被作为参数传递时带来的性能提升.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void Proc (int & lvalue) { ++lvalue; cout<< "increment the lvalue`data: " << lvalue <<endl; } void Proc (int && rvalue) { ++rvalue; cout<< "increment the rvalue`data: " << rvalue <<endl; } int a = 9 , b = 10 ,c = 2 ;Proc (a); Proc (b+c);
// Output 10 13
对于编译器报错“无法将int型转化为int&”我们可以这么理解:熟悉汇编的同学应该知道 Proc(b+c)的值传递过程,是由b和c的值在相加后产生的中间值赋值给寄存器作为参数传递给函数,而寄存器中的变量是临时变量,不同于堆栈上的变量(可以由引用地址确定一个唯一的变量值)。因此由于无法将临时的整型变量转化为有效的地址引用,编译器将无法通过。
另外无法同时定义Proc(int vlaue)和Proc(int& value),因为这两个函数在遇到一个普通int参数时会产生歧义,二者都可以被调用。同样的无法同时定义Proc(int vlaue)和Proc(int&& value),原因是由于在遇到一个临时int变量时,二者也都是可以被调用的,同样有二义性。因此同时定义Proc(int& value)和Proc(int&& value)成为新的选择。下面的遇到的示例也是和这个相同的原因。
所以接下来的讨论都是以引用作为参数,如果参数类型是值传递 int value,那么就不存在这些问题了(未定义Proc(int&& rvalue),Proc(b+c)也可以正常运作,它将很自然地调用值传递版本),右值引用就是为了减少参数副本开销而用的.
C++11提供了std::move()函数将左值强制转换为右值,下面会演示它的用法.
2、移动语义(Move Semantics)
移动语义实现了移动构造函数(move copy constructor)和移动赋值预算符(move assignment operator).
移动语义将上一概念中的右值引用引出的临时对象转化为可以被引用的左值(在参数场合下,即转为实参),这个动作实际上提供了程序员操作临时对象内存的能力(the possibility to modify the temporary object or memory) ,并且允许我们自由地选择移动构造和移动赋值的操作
1 2 3 MyClass (MyClass&& src); MyClass& operator =(MyClass&& rhs);
考虑下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 class MyClass { public : MyClass (double vlaue); MyClass (const MyClass& src); MyClass& operator =(const MyClass& rhs); MyClass (MyClass&& src); MyClass& operator =(MyClass&& rhs); double m_data; }; MyClass::MyClass (double value ) :m_data (value) { } MyClass::MyClass (const MyClass& src) { m_data = src.m_data; cout << "Normal copy constructor" <<endl; } MyClass& MyClass::operator =(const MyClass& rhs) { if (this == &rhs) { return *this ; } m_data = rhs.m_data; cout << "Normal assignment operator" <<endl; return *this ; } MyClass::MyClass (MyClass&& src) { m_data = src.m_data; src.m_data = 0 ; cout << "Move copy constructor" <<endl; } MyClass& MyClass::operator =(MyClass&& rhs) { if (this == &rhs) { return *this ; } m_data = rhs.m_data; cout<< "Move assignment operator" <<endl; return *this ; } MyClass CreateObject (double value) { return MyClass (value); } void Func (MyClass& a) { cout<< "Called Func(MyClass& a)" << endl; } void Func (MyClass&& a) { cout<< "Called Func(MyClass&& a)" << endl; } int main (int argc, char * argv[]) { MyClass a (3.0 ) ; Func (a); Func (3.0 ); Func (std::move (2.0 )); Func (CreateObject (1.0 )); cout<< "Before move assignment, a.m_data = " <<a.m_data << endl; a = CreateObject (4.0 ); cout<< "After move assignment, a.m_data = " <<a.m_data << endl; MyClass d (std::move(a)) ; cout<< "After move copy constructor," << endl; cout<< "a.m_data = " << a.m_data << endl; cout<< "d.m_data = " <<d.m_data << endl; return 0 ; }
输出如下:
1 2 3 4 5 6 7 8 9 10 11 Called Func (MyClass& a) Called Func (MyClass&& a) Called Func (MyClass&& a) Called Func (MyClass&& a) Before move assignment, a.m_data = 3 Move assignment operator After move assignment, a.m_data = 4 Move copy constructor After move copy constructor, a.m_data = 0 d.m_data = 4
现在我们加上析构函数:
1 2 3 4 MyClass::~MyClass () { cout<<"Deconstructor called" <<endl; }
通过vector来查看各个函数被调用情况,vector在增加元素时会重新分配内存空间,这时候vector内部会重新开辟空间,并复制构造出新对象,然后销毁原内存中的旧对象。代码如下:
1 2 3 vector<MyClass> vec; vec.push_back(CreateObject(9.0)); vec.push_back(std::move(10.0));
在未定义移动拷贝构造函数MyClass(MyClass&& src)时,调用普通拷贝构造函数MyClass(const MyClass& src)执行深拷贝,输出如下:
1 2 3 4 5 6 7 8 Normal copy constructor Deconstructor called Normal copy constructor Deconstructor called Normal copy constructor Deconstructor called Deconstructor called Deconstructor called
在同时定义移动拷贝构造函数MyClass(MyClass&& src)和普通拷贝构造函数MyClass(const MyClass& src)时,输出如下:
1 2 3 4 5 6 7 8 Move copy constructor Deconstructor called Move copy constructor Deconstructor called Move copy constructor Deconstructor called Deconstructor called Deconstructor called
可见在临时对象发生右值引用时会查询是否存在移动构造函数(或移动赋值运算)的定义,如果存在,将会选择后者执行更快的临时对象数据获取,否则执行默认的深拷贝.
可以以C++11之2——类的构造
中相同的方式= default(或=delete)来开启或禁用两个函数.