左值引用、右值引用和移动语义
【1】左值引用和右值引用
左值引用(一般所谓的引用形式)使标识符关联到左值。
何为左值?左值是一个表示数据的表达式(如变量名、解除引用的指针)。
最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符(即不能给它赋值,但可获取其地址)。
1 int n = 10; 2 int * pt = new int; 3 int & rn = n; // 变量名 4 int & rt = *pt; // 解除引用的指针 5 const int b = 20; // 不可以给b赋值,但可获取其地址 6 // b = 21; // error 7 const int * pb = &b; // ok 8 const int & rb = b; // 不可以给rb赋值,但可获取其地址 9 // rb = n; // error 10 const int * prb = &rb; // ok
总而言之,判断左值的唯一条件是程序可获取其地址(即可对其应用地址运算符)。
右值引用使标识符关联到右值。右值引用是使用&&表示的。右值即可出现在赋值表达式右边,但不能对其应用地址运算符的值(与左值相反)。
右值包括字面常量(C-风格字符串除外,因为它表示地址),比如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)。
举例如下:
1 int x = 10; 2 int y = 11; 3 int && r1 = 12; // 常量 4 int && r2 = x + y; // 表达式 5 double && r3 = std::sqrt(2.0); // 函数返回值
通过示例可以发现:将右值关联到右值引用导致该右值被存储到特定的位置,并且可通过标识符获取该位置的地址。也就是说,虽然不可将地址运算符&应用于12,但可将其用于r1。将数据与特定的地址关联,使得可通过右值引用来访问该数据信息。
下面演示及验证右值引用的作用:
1 #include <iostream> 2 using namespace std; 3 4 double f(double tf) { return tf/20; }; 5 6 void main() 7 { 8 double tc = 10.5; 9 double && rd1 = 100.01; 10 double && rd2 = 1.8 * tc; 11 double && rd3 = f(rd2); 12 cout << "tc Value And Address: " << tc << " " << &tc << endl; 13 cout << "rd1 Value And Address: " << rd1 << " " << &rd1 << endl; 14 cout << "rd2 Value And Address: " << rd2 << " " << &rd2 << endl; 15 cout << "rd3 Value And Address: " << rd3 << " " << &rd3 << endl; 16 cin.get(); 17 } 18 // run out 19 /* 20 tc Value And Address: 10.5 003FFAE4 21 rd1 Value And Address: 100.01 003FFAC8 22 rd2 Value And Address: 18.9 003FFAAC 23 rd3 Value And Address: 0.945 003FFA90 24 */
为啥需要右值引用呢?引入右值引用的主要目的之一是实现移动语义。
【2】移动语义之移动构造函数
2.1 为何需要移动语义?
请看如下示例代码(相信你我已经写过很多类似的),只有复制构造函数情况下:
1 // 例1:只有复制构造函数 2 #include <iostream> 3 using namespace std; 4 5 // interface 6 class Useless 7 { 8 private: 9 int n; // number of elements 10 char * pc; // pointer to data 11 static int ct; // number of objects 12 void ShowObject() const; 13 14 public: 15 Useless(); 16 explicit Useless(int k); 17 Useless(int k, char ch); 18 Useless(const Useless & f); // regular copy constructor 19 ~Useless(); 20 Useless operator+(const Useless & f)const; 21 void ShowData() const; 22 }; 23 24 // implementation 25 int Useless::ct = 0; 26 27 Useless::Useless() 28 { 29 ++ct; 30 n = 0; 31 pc = nullptr; 32 cout << "default constructor called; number of objects: " << ct << endl; 33 ShowObject(); 34 } 35 36 Useless::Useless(int k) : n(k) 37 { 38 ++ct; 39 cout << "Useless(int k) constructor called; number of objects: " << ct << endl; 40 pc = new char[n]; 41 ShowObject(); 42 } 43 44 Useless::Useless(int k, char ch) : n(k) 45 { 46 ++ct; 47 cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; 48 pc = new char[n]; 49 for (int i = 0; i < n; i++) 50 pc[i] = ch; 51 ShowObject(); 52 } 53 54 Useless::Useless(const Useless & f) : n(f.n) 55 { 56 ++ct; 57 cout << "copy constructor const called; number of objects: " << ct << endl; 58 pc = new char[n]; 59 for (int i = 0; i < n; i++) 60 pc[i] = f.pc[i]; 61 ShowObject(); 62 } 63 64 Useless::~Useless() 65 { 66 cout << "destructor called; "; 67 cout << "deleted object:\n"; 68 ShowObject(); 69 delete [] pc; 70 cout << "objects left: " << --ct << endl << endl; 71 } 72 73 Useless Useless::operator+(const Useless & f)const 74 { 75 cout << "Entering operator+()\n"; 76 Useless temp = Useless(n + f.n); 77 for (int i = 0; i < n; i++) 78 temp.pc[i] = pc[i]; 79 for (int i = n; i < temp.n; i++) 80 temp.pc[i] = f.pc[i - n]; 81 cout << "temp object:\n"; 82 cout << "Leaving operator+()\n"; 83 return temp; 84 } 85 86 void Useless::ShowObject() const 87 { 88 cout << "Number of elements: " << n; 89 cout << " Data address: " << (void *) pc << endl << endl; 90 } 91 92 void Useless::ShowData() const 93 { 94 if (0 == n) 95 { 96 cout << "(object empty)"; 97 } 98 else 99 { 100 for (int i = 0; i < n; i++) 101 cout << pc[i]; 102 } 103 cout << endl; 104 } 105 106 // application 107 int main() 108 { 109 { 110 Useless one(10, 'x'); 111 Useless two = one; // calls copy constructor 112 Useless three(20, 'o'); 113 Useless four(one + three); // calls operator+(), copy constructor 114 cout << "object one: "; 115 one.ShowData(); 116 cout << "object two: "; 117 two.ShowData(); 118 cout << "object three: "; 119 three.ShowData(); 120 cout << "object four: "; 121 four.ShowData(); 122 } 123 cin.get(); 124 } 125 126 // out 127 /* 128 Useless(int k, char ch) constructor called; number of objects: 1 129 Number of elements: 10 Data address: 004A4910 130 131 copy constructor const called; number of objects: 2 132 Number of elements: 10 Data address: 004A4958 133 134 Useless(int k, char ch) constructor called; number of objects: 3 135 Number of elements: 20 Data address: 004A49A0 136 137 Entering operator+() 138 Useless(int k) constructor called; number of objects: 4 139 Number of elements: 30 Data address: 004A49F0 140 141 temp object: 142 Leaving operator+() 143 copy constructor const called; number of objects: 5 144 Number of elements: 30 Data address: 004A4C50 145 146 destructor called; deleted object: 147 Number of elements: 30 Data address: 004A49F0 148 149 objects left: 4 150 151 object one: xxxxxxxxxx 152 object two: xxxxxxxxxx 153 object three: oooooooooooooooooooo 154 object four: xxxxxxxxxxoooooooooooooooooooo 155 destructor called; deleted object: 156 Number of elements: 30 Data address: 004A4C50 157 158 objects left: 3 159 160 destructor called; deleted object: 161 Number of elements: 20 Data address: 004A49A0 162 163 objects left: 2 164 165 destructor called; deleted object: 166 Number of elements: 10 Data address: 004A4958 167 168 objects left: 1 169 170 destructor called; deleted object: 171 Number of elements: 10 Data address: 004A4910 172 173 objects left: 0 174 175 */
运行结果分析如下:
从运算符+重载开始琢磨,如下:
1 Useless Useless::operator+(const Useless & f)const 2 { 3 cout << "Entering operator+()\n"; 4 Useless temp = Useless(n + f.n); 5 for (int i = 0; i < n; i++) 6 temp.pc[i] = pc[i]; 7 for (int i = n; i < temp.n; i++) 8 temp.pc[i] = f.pc[i - n]; 9 cout << "temp object:\n"; 10 cout << "Leaving operator+()\n"; 11 return temp; 12 }
运算符+重载实现中,创建了一个局部对象temp(注意与临时对象的区别),最终函数结束时返回该对象。
由于函数返回值类型,所以这里只能调用复制构造函数来创建对象four。如下:
1 Useless::Useless(const Useless & f) : n(f.n) 2 { 3 ++ct; 4 cout << "copy constructor const called; number of objects: " << ct << endl; 5 pc = new char[n]; 6 for (int i = 0; i < n; i++) 7 pc[i] = f.pc[i]; 8 ShowObject(); 9 }
因此复制构造函数的形参f直接指向该局部对象(即实参),通过另开辟空间、深拷贝该局部对象从而完成创建对象four的任务。
然后,析构掉该局部对象。关键这里白白析构掉这个局部对象申请的内存空间的确有点太奢侈了,尤其从时间方面权衡整个过程。
如果,有一种构造函数可让对象four的成员变量pc指针直接指向该局部对象已开辟的内存空间,而不需用再像复制构造函数那样又另开辟空间来创建对象four。
那么,这个过程将节省多少时间呢?这里要点是做了大量的无用功。即就是说,将临时对象的所有权直接交给对象four,不是更完美吗?
2.2 移动语义是什么?
请看如下示例,增加移动构造函数:
1 // 例2:复制构造函数 与 移动构造函数 2 // useless.cpp -- an otherwise useless class with move semantics 3 #include <iostream> 4 using namespace std; 5 6 // interface 7 class Useless 8 { 9 private: 10 int n; // number of elements 11 char * pc; // pointer to data 12 static int ct; // number of objects 13 void ShowObject() const; 14 15 public: 16 Useless(); 17 explicit Useless(int k); 18 Useless(int k, char ch); 19 Useless(const Useless & f); // regular copy constructor 20 Useless(Useless && f); // move constructor 21 Useless & operator=(const Useless & f); // copy assignment 22 Useless & operator=(Useless && f); // move assignment 23 ~Useless(); 24 Useless operator+(const Useless & f) const; 25 void ShowData() const; 26 }; 27 28 // implementation 29 int Useless::ct = 0; 30 31 Useless::Useless() 32 { 33 ++ct; 34 n = 0; 35 pc = nullptr; 36 cout << "default constructor called; number of objects: " << ct << endl; 37 ShowObject(); 38 } 39 40 Useless::Useless(int k) : n(k) 41 { 42 ++ct; 43 cout << "Useless(int k) constructor called; number of objects: " << ct << endl; 44 pc = new char[n]; 45 ShowObject(); 46 } 47 48 Useless::Useless(int k, char ch) : n(k) 49 { 50 ++ct; 51 cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; 52 pc = new char[n]; 53 for (int i = 0; i < n; i++) 54 pc[i] = ch; 55 ShowObject(); 56 } 57 58 Useless::Useless(const Useless & f) : n(f.n) 59 { 60 ++ct; 61 cout << "copy constructor const called; number of objects: " << ct << endl; 62 pc = new char[n]; 63 for (int i = 0; i < n; i++) 64 pc[i] = f.pc[i]; 65 ShowObject(); 66 } 67 68 Useless::Useless(Useless && f) : n(f.n) 69 { 70 ++ct; 71 cout << "move constructor called; number of objects: " << ct << endl; 72 pc = f.pc; // steal address 73 f.pc = nullptr; // give old object nothing in return 74 f.n = 0; 75 ShowObject(); 76 } 77 78 Useless & Useless::operator=(const Useless & f) 79 { 80 if (this == &f) 81 return *this; 82 delete []pc; 83 n = f.n; 84 pc = new char[n]; 85 for (int i = 0; i < n; ++i) 86 pc[i] = f.pc[i]; 87 return *this; 88 } 89 90 Useless & Useless::operator=(Useless && f) 91 { 92 if (this == &f) 93 return *this; 94 delete []pc; 95 n = f.n; 96 pc = f.pc; 97 f.n = 0; 98 f.pc = nullptr; 99 return *this; 100 } 101 102 Useless::~Useless() 103 { 104 cout << "destructor called; "; 105 cout << "deleted object:\n"; 106 ShowObject(); 107 delete [] pc; 108 cout << "objects left: " << --ct << endl << endl; 109 } 110 111 Useless Useless::operator+(const Useless & f)const 112 { 113 cout << "Entering operator+()\n"; 114 Useless temp = Useless(n + f.n); 115 for (int i = 0; i < n; i++) 116 temp.pc[i] = pc[i]; 117 for (int i = n; i < temp.n; i++) 118 temp.pc[i] = f.pc[i - n]; 119 cout << "temp object:\n"; 120 cout << "Leaving operator+()\n"; 121 return temp; 122 } 123 124 void Useless::ShowObject() const 125 { 126 cout << "Number of elements: " << n; 127 cout << " Data address: " << (void *) pc << endl << endl; 128 } 129 130 void Useless::ShowData() const 131 { 132 if (0 == n) 133 { 134 cout << "(object empty)"; 135 } 136 else 137 { 138 for (int i = 0; i < n; i++) 139 cout << pc[i]; 140 } 141 cout << endl; 142 } 143 144 // application 145 int main() 146 { 147 { 148 Useless one(10, 'x'); 149 Useless two = one; // calls copy constructor 150 Useless three(20, 'o'); 151 Useless four(one + three); // calls operator+(), move constructor 152 cout << "object one: "; 153 one.ShowData(); 154 cout << "object two: "; 155 two.ShowData(); 156 cout << "object three: "; 157 three.ShowData(); 158 cout << "object four: "; 159 four.ShowData(); 160 } 161 cin.get(); 162 } 163 164 // out 165 /* 166 Useless(int k, char ch) constructor called; number of objects: 1 167 Number of elements: 10 Data address: 00224910 168 169 copy constructor const called; number of objects: 2 170 Number of elements: 10 Data address: 00224958 171 172 Useless(int k, char ch) constructor called; number of objects: 3 173 Number of elements: 20 Data address: 002249A0 174 175 Entering operator+() 176 Useless(int k) constructor called; number of objects: 4 177 Number of elements: 30 Data address: 002249F0 178 179 temp object: 180 Leaving operator+() 181 move constructor called; number of objects: 5 182 Number of elements: 30 Data address: 002249F0 183 184 destructor called; deleted object: 185 Number of elements: 0 Data address: 00000000 186 187 objects left: 4 188 189 object one: xxxxxxxxxx 190 object two: xxxxxxxxxx 191 object three: oooooooooooooooooooo 192 object four: xxxxxxxxxxoooooooooooooooooooo 193 destructor called; deleted object: 194 Number of elements: 30 Data address: 002249F0 195 196 objects left: 3 197 198 destructor called; deleted object: 199 Number of elements: 20 Data address: 002249A0 200 201 objects left: 2 202 203 destructor called; deleted object: 204 Number of elements: 10 Data address: 00224958 205 206 objects left: 1 207 208 destructor called; deleted object: 209 Number of elements: 10 Data address: 00224910 210 211 objects left: 0 212 213 */
运行结果分析如下:
注意:上面分析运行结果图中,把temp称为局部变量,这次运行结果分析图中,把temp称为了局部对象。
实质上,变量和对象是一回事,只不过人们习惯上把用内置类型定义的东东称为变量,而把自定义类型定义的东东称为对象(与类相匹配)。
通过实践,我们发现:移动构造函数可以完美实现夙愿。
从运行结果看到内存地址002249F0出现了三次:首次出现在构建局部对象时,二次出现在调用移动构造函数创建对象four时,第三次出现在析构对象four时。
很显然,对象four的创建通过调用移动构造函数而没有再另申请内存空间,仅仅只是改变了局部对象实质内容的所有权而已。
而观察局部对象析构时的打印信息:其指针成员值为空。目的为了防止对同一块内存多次delete引起程序的致命性错误。因为对空指针多次delete没有任何意义。
如何理解程序中就多次delete同一块内存了呢?
第一次,析构局部对象temp,其实释放的正是内存002249F0,只不过在析构之前移动构造函数将其已移动完成并置为空。
第二次,析构对象four,释放的也是内存002249F0。(还没有看懂的话,可对比上面只有复制构造函数的程序运行结果再琢磨琢磨其中的套路。)
总结:这个过程类似于在计算机中移动文件的情形:实际文件还留在原来的地方,只不过仅仅修改记录而已,这种方法被称为移动语义。
实质上,移动语义即不移动原始数据,仅仅修改了记录(指针指向)而已。
2.3 C++11如何支持移动语义?
要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要,而这正是右值引用发挥作用的地方。
可定义两个构造函数,其中一个是常规复制构造函数,它使用const左值引用作为参数,这个引用关联到左值实参;
另一个是移动构造函数,它使用右值引用作为参数,该引用关联到右值实参。复制构造函数可执行深复制,而移动构造函数只调整记录。
在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,这意味着右值引用参数不应用const修饰。
虽然使用右值引用可支持移动语义,但这并不会神奇的发生。要让移动语义发生,需要两个步骤:
1. 右值引用让编译器知道何时可使用移动语义。
比如,上面的示例中对象one是左值,与左值引用匹配,而表达式one + three是右值,与右值引用匹配。
2. 为自定义类编写移动构造函数,使其具备所需的行为能力。
【3】移动语义之移动赋值运算符
适用于构造函数的移动语义考虑也适用于赋值运算符。
示例1. 只有赋值运算符函数
1 // 只有复制赋值运算符 2 #include <iostream> 3 using namespace std; 4 5 // interface 6 class Useless 7 { 8 private: 9 int n; // number of elements 10 char * pc; // pointer to data 11 static int ct; // number of objects 12 void ShowObject() const; 13 14 public: 15 Useless(); 16 explicit Useless(int k); 17 Useless(int k, char ch); 18 Useless(const Useless & f); // regular copy constructor 19 Useless(Useless && f); // move constructor 20 Useless & operator=(const Useless & f); // copy assignment 21 ~Useless(); 22 Useless operator+(const Useless & f)const; 23 void ShowData() const; 24 }; 25 26 // implementation 27 int Useless::ct = 0; 28 29 Useless::Useless() 30 { 31 ++ct; 32 n = 0; 33 pc = nullptr; 34 cout << "default constructor called; number of objects: " << ct << endl; 35 ShowObject(); 36 } 37 38 Useless::Useless(int k) : n(k) 39 { 40 ++ct; 41 cout << "Useless(int k) constructor called; number of objects: " << ct << endl; 42 pc = new char[n]; 43 ShowObject(); 44 } 45 46 Useless::Useless(int k, char ch) : n(k) 47 { 48 ++ct; 49 cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; 50 pc = new char[n]; 51 for (int i = 0; i < n; i++) 52 pc[i] = ch; 53 ShowObject(); 54 } 55 56 Useless::Useless(const Useless & f) : n(f.n) 57 { 58 ++ct; 59 cout << "copy const called; number of objects: " << ct << endl; 60 pc = new char[n]; 61 for (int i = 0; i < n; i++) 62 pc[i] = f.pc[i]; 63 ShowObject(); 64 } 65 66 Useless::Useless(Useless && f) : n(f.n) 67 { 68 ++ct; 69 cout << "move constructor called; number of objects: " << ct << endl; 70 pc = f.pc; // steal address 71 f.pc = nullptr; // give old object nothing in return 72 f.n = 0; 73 ShowObject(); 74 } 75 76 Useless & Useless::operator=(const Useless & f) 77 { 78 cout << "copy assignment operator= called;\n"; 79 if (this == &f) 80 return *this; 81 delete []pc; 82 n = f.n; 83 pc = new char[n]; 84 for (int i = 0; i < n; ++i) 85 pc[i] = f.pc[i]; 86 return *this; 87 } 88 89 Useless::~Useless() 90 { 91 cout << "destructor called; deleted object: "; 92 ShowObject(); 93 delete [] pc; 94 cout << "objects left: " << --ct << endl; 95 } 96 97 Useless Useless::operator+(const Useless & f)const 98 { 99 cout << "Entering operator+()\n"; 100 Useless temp = Useless(n + f.n); 101 for (int i = 0; i < n; i++) 102 temp.pc[i] = pc[i]; 103 for (int i = n; i < temp.n; i++) 104 temp.pc[i] = f.pc[i - n]; 105 cout << "temp object:\n"; 106 cout << "Leaving operator+()\n"; 107 return temp; 108 } 109 110 void Useless::ShowObject() const 111 { 112 cout << "Number of elements: " << n; 113 cout << " Data address: " << (void *) pc << endl; 114 } 115 116 void Useless::ShowData() const 117 { 118 if (0 == n) 119 { 120 cout << "(object empty)"; 121 } 122 else 123 { 124 for (int i = 0; i < n; i++) 125 cout << pc[i]; 126 } 127 cout << endl; 128 } 129 130 // application 131 int main() 132 { 133 { 134 Useless one(10, 'x'); 135 Useless two = one + one; // calls move constructor 136 cout << "Object one: "; 137 one.ShowData(); 138 cout << "Object two: "; 139 two.ShowData(); 140 Useless three, four; 141 cout << "three = one \n"; 142 three = one; // automatic copy assignment 143 cout << "Now Object three = "; 144 three.ShowData(); 145 cout << "And Object one = "; 146 one.ShowData(); 147 cout << "four = one + two \n"; 148 four = one + two; // automatic move assignment 149 cout << "Now Object four = "; 150 four.ShowData(); 151 cout << "four = move(one)\n"; 152 four = std::move(one); // forced move assignment 153 cout << "Now Object four = "; 154 four.ShowData(); 155 cout << "And Object one = "; 156 one.ShowData(); 157 } 158 cin.get(); 159 } 160 161 // run out 162 /* 163 Useless(int k, char ch) constructor called; number of objects: 1 164 Number of elements: 10 Data address: 00794910 165 Entering operator+() 166 Useless(int k) constructor called; number of objects: 2 167 Number of elements: 20 Data address: 00794958 168 temp object: 169 Leaving operator+() 170 move constructor called; number of objects: 3 171 Number of elements: 20 Data address: 00794958 172 destructor called; deleted object: Number of elements: 0 Data address: 00000000 173 objects left: 2 174 Object one: xxxxxxxxxx 175 Object two: xxxxxxxxxxxxxxxxxxxx 176 default constructor called; number of objects: 3 177 Number of elements: 0 Data address: 00000000 178 default constructor called; number of objects: 4 179 Number of elements: 0 Data address: 00000000 180 three = one 181 copy assignment operator= called; 182 Now Object three = xxxxxxxxxx 183 And Object one = xxxxxxxxxx 184 four = one + two 185 Entering operator+() 186 Useless(int k) constructor called; number of objects: 5 187 Number of elements: 30 Data address: 007949F0 188 temp object: 189 Leaving operator+() 190 move constructor called; number of objects: 6 191 Number of elements: 30 Data address: 007949F0 192 destructor called; deleted object: Number of elements: 0 Data address: 00000000 193 objects left: 5 194 copy assignment operator= called; 195 destructor called; deleted object: Number of elements: 30 Data address: 007949F0 196 197 objects left: 4 198 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 199 four = move(one) 200 copy assignment operator= called; 201 Now Object four = xxxxxxxxxx 202 And Object one = xxxxxxxxxx 203 destructor called; deleted object: Number of elements: 10 Data address: 007949F0 204 205 objects left: 3 206 destructor called; deleted object: Number of elements: 10 Data address: 007949A8 207 208 objects left: 2 209 destructor called; deleted object: Number of elements: 20 Data address: 00794958 210 211 objects left: 1 212 destructor called; deleted object: Number of elements: 10 Data address: 00794910 213 214 objects left: 0 215 216 */
由于没有写移动赋值运算符函数,以上执行结果不做分析(可对比下面的分析更充分的理解)。
示例2. 有赋值运算符函数,有移动赋值运算符函数
1 // 有复制赋值运算符 且 有移动赋值运算符 2 // useless.cpp -- an otherwise useless class with move semantics 3 #include <iostream> 4 using namespace std; 5 6 // interface 7 class Useless 8 { 9 private: 10 int n; // number of elements 11 char * pc; // pointer to data 12 static int ct; // number of objects 13 void ShowObject() const; 14 15 public: 16 Useless(); 17 explicit Useless(int k); 18 Useless(int k, char ch); 19 Useless(const Useless & f); // regular copy constructor 20 Useless(Useless && f); // move constructor 21 Useless & operator=(const Useless & f); // copy assignment 22 Useless & operator=(Useless && f); // move assignment 23 ~Useless(); 24 Useless operator+(const Useless & f)const; 25 void ShowData() const; 26 }; 27 28 // implementation 29 int Useless::ct = 0; 30 31 Useless::Useless() 32 { 33 ++ct; 34 n = 0; 35 pc = nullptr; 36 cout << "default constructor called; number of objects: " << ct << endl; 37 ShowObject(); 38 } 39 40 Useless::Useless(int k) : n(k) 41 { 42 ++ct; 43 cout << "Useless(int k) constructor called; number of objects: " << ct << endl; 44 pc = new char[n]; 45 ShowObject(); 46 } 47 48 Useless::Useless(int k, char ch) : n(k) 49 { 50 ++ct; 51 cout << "Useless(int k, char ch) constructor called; number of objects: " << ct << endl; 52 pc = new char[n]; 53 for (int i = 0; i < n; i++) 54 pc[i] = ch; 55 ShowObject(); 56 } 57 58 Useless::Useless(const Useless & f) : n(f.n) 59 { 60 ++ct; 61 cout << "copy const called; number of objects: " << ct << endl; 62 pc = new char[n]; 63 for (int i = 0; i < n; i++) 64 pc[i] = f.pc[i]; 65 ShowObject(); 66 } 67 68 Useless::Useless(Useless && f) : n(f.n) 69 { 70 ++ct; 71 cout << "move constructor called; number of objects: " << ct << endl; 72 pc = f.pc; // steal address 73 f.pc = nullptr; // give old object nothing in return 74 f.n = 0; 75 ShowObject(); 76 } 77 78 Useless & Useless::operator=(const Useless & f) 79 { 80 cout << "copy assignment operator= called;\n"; 81 if (this == &f) 82 return *this; 83 delete []pc; 84 n = f.n; 85 pc = new char[n]; 86 for (int i = 0; i < n; ++i) 87 pc[i] = f.pc[i]; 88 return *this; 89 } 90 91 Useless & Useless::operator=(Useless && f) 92 { 93 cout << "move assignment operator= called;\n"; 94 if (this == &f) 95 return *this; 96 delete []pc; 97 n = f.n; 98 pc = f.pc; 99 f.n = 0; 100 f.pc = nullptr; 101 return *this; 102 } 103 104 Useless::~Useless() 105 { 106 cout << "destructor called; deleted object: "; 107 ShowObject(); 108 delete [] pc; 109 cout << "objects left: " << --ct << endl; 110 } 111 112 Useless Useless::operator+(const Useless & f)const 113 { 114 cout << "Entering operator+()\n"; 115 Useless temp = Useless(n + f.n); 116 for (int i = 0; i < n; i++) 117 temp.pc[i] = pc[i]; 118 for (int i = n; i < temp.n; i++) 119 temp.pc[i] = f.pc[i - n]; 120 cout << "temp object:\n"; 121 cout << "Leaving operator+()\n"; 122 return temp; 123 } 124 125 void Useless::ShowObject() const 126 { 127 cout << "Number of elements: " << n; 128 cout << " Data address: " << (void *) pc << endl; 129 } 130 131 void Useless::ShowData() const 132 { 133 if (0 == n) 134 { 135 cout << "(object empty)"; 136 } 137 else 138 { 139 for (int i = 0; i < n; i++) 140 cout << pc[i]; 141 } 142 cout << endl; 143 } 144 145 // application 146 int main() 147 { 148 { 149 Useless one(10, 'x'); 150 Useless two = one + one; // calls move constructor 151 cout << "Object one: "; 152 one.ShowData(); 153 cout << "Object two: "; 154 two.ShowData(); 155 Useless three, four; 156 cout << "three = one \n"; 157 three = one; // automatic copy assignment 158 cout << "Now Object three = "; 159 three.ShowData(); 160 cout << "And Object one = "; 161 one.ShowData(); 162 cout << "four = one + two \n"; 163 four = one + two; // automatic move assignment 164 cout << "Now Object four = "; 165 four.ShowData(); 166 cout << "four = move(one)\n"; 167 four = std::move(one); // forced move assignment 168 cout << "Now Object four = "; 169 four.ShowData(); 170 cout << "And Object one = "; 171 one.ShowData(); 172 } 173 cin.get(); 174 } 175 /* 176 Useless(int k, char ch) constructor called; number of objects: 1 177 Number of elements: 10 Data address: 00204910 178 Entering operator+() 179 Useless(int k) constructor called; number of objects: 2 180 Number of elements: 20 Data address: 00204958 181 temp object: 182 Leaving operator+() 183 move constructor called; number of objects: 3 184 Number of elements: 20 Data address: 00204958 185 destructor called; deleted object: Number of elements: 0 Data address: 00000000 186 objects left: 2 187 Object one: xxxxxxxxxx 188 Object two: xxxxxxxxxxxxxxxxxxxx 189 default constructor called; number of objects: 3 190 Number of elements: 0 Data address: 00000000 191 default constructor called; number of objects: 4 192 Number of elements: 0 Data address: 00000000 193 three = one 194 copy assignment operator= called; 195 Now Object three = xxxxxxxxxx 196 And Object one = xxxxxxxxxx 197 four = one + two 198 Entering operator+() 199 Useless(int k) constructor called; number of objects: 5 200 Number of elements: 30 Data address: 002049F0 201 temp object: 202 Leaving operator+() 203 move constructor called; number of objects: 6 204 Number of elements: 30 Data address: 002049F0 205 destructor called; deleted object: Number of elements: 0 Data address: 00000000 206 objects left: 5 207 move assignment operator= called; 208 destructor called; deleted object: Number of elements: 0 Data address: 00000000 209 objects left: 4 210 Now Object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 211 four = move(one) 212 move assignment operator= called; 213 Now Object four = xxxxxxxxxx 214 And Object one = (object empty) 215 destructor called; deleted object: Number of elements: 10 Data address: 00204910 216 217 objects left: 3 218 destructor called; deleted object: Number of elements: 10 Data address: 002049A8 219 220 objects left: 2 221 destructor called; deleted object: Number of elements: 20 Data address: 00204958 222 223 objects left: 1 224 destructor called; deleted object: Number of elements: 0 Data address: 00000000 225 objects left: 0 226 */
运作结果分析如下:
移动赋值运算符函数如下:
1 Useless & Useless::operator=(Useless && f) 2 { 3 cout << "move assignment operator= called;\n"; 4 if (this == &f) 5 return *this; 6 delete []pc; 7 n = f.n; 8 pc = f.pc; 9 f.n = 0; 10 f.pc = nullptr; 11 return *this; 12 }
移动赋值运算符删除目标对象中的原始数据,并将源对象的所有权转让给目标对象。不能让多个指针指向相同的数据(同上移动构造函数的原理)。
【4】强制移动
移动构造函数和移动赋值运算符使用右值。如果想要强制移动,即要让它们使用左值作为实参,可使用运算符static_cast<>将对象的类型强制转换为Useless &&。
但C++11提供了更简单的方式——使用头文件utility中声明的函数std::move()。如上示例中已使用。
通过例子比较可知,函数std::move()并非一定会导致移动操作。表达式std::move(one)是右值,因此上例赋值语句将调用其移动赋值运算符(前提条件是定义了移动赋值运算符)。
如果没有定义移动赋值运算符,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将根本不允许上例的赋值。
Good Good Study, Day Day Up.
顺序 选择 循环 总结