std::get<C++11多线程库>(05): 右值引用--移动语义--函数模板
1 #include <QCoreApplication> 2 #include <iostream> 3 #include <vector> 4 #include <assert.h> 5 6 #define Has_Move 7 /* 8 * 话题:右值引用+移动语义+函数模板 9 * 一、右值引用 10 * 1. C++的引用允许你为已经存在的对象创建一个新的名字。对新引用所做的访问和修改操作,都会影响它的原型。 11 * 2. C++11之前只有左值引用。 12 * lvalue这个词来自于C语言,指的是可以放在赋值表达式左边的事物——在栈上或堆上分配的命名对象,或者其他对象成员——有明确的内存地址; 13 * rvalue这个词也来源于C语言,指的是可以出现在赋值表达式右侧的对象——例如,文字常量和临时变量。因此,左值引用只能被绑定在左值上,而不是右值。 14 * 15 * 3. 右值不可以赋值给左值引用, 例如: int &i = 27; 16 * 4. 右值可以赋值给左值的const引用, 例如:int const& i = 27; 也可以右值赋值给 const的左值引用 const int &i = 27; 17 * 5. C++11标准介绍了右值引用(rvalue reference),这种方式只能绑定右值,不能绑定左值。 例如:int&& i = 27; 18 * 6. 有个有趣的现象: 右值引用可以赋值给左值的const引用,也可以赋值给 const的左值引用,但是反过来赋值不可以。 19 * 20 * 二、移动语义 21 * 7. C++11新添语义——移动语义(move semantics)。 22 * 8. 右值通常都是临时的。 23 * 例如:函数返回值; 常量做参数传递; 24 * 9. 当传递一个右值时,不会发生对象的拷贝, 而是一种“移动”,或者说是一种“窃取”。 25 * 直接把对象从原来的所有者那里“窃取”过来,并且对象的所有状态不会发生改变。没有拷贝的发生,会节省很多的内存分配,性能更好。 26 * 例如:需要传递一个 std::vector<int>,值传递时,需要分配同等大小的内存空间。 27 * 10. C++标准库不会将一个对象显式的转移到另一个对象中,除非该对象将销毁的时候,或被赋值的时候(拷贝和移动的操作很相似)。 28 * 参考:问-思考:2 29 * 30 * 三、右值引用与函数模板 31 * 11. 当右值引用作为函数模板的参数时,实参既可以是左值,也可以是右值。 32 * 因右值引用形如:T&& t,在实参像形参匹配的过程中, 有可能匹配为 TT & t(TT=T&), 也可能匹配为 TT t(TT=T&&)。 33 * 因此会有这样的总结:如果函数模板的参数是右值引用, 34 * 当实参为左值时, 模板会把实参类型当作左值引用,即 TT & t(TT=T&); 35 * 当实参为右值时, 模板会把实参类型当作普通数据使用。 即 TT t(TT=T&&) 36 * 37 * 38 * 12. 很显然,左值移动 和 右值移动 可以作为函数重载。 39 * 构造函数和移动构造函数就能说明这一点。 40 * 41 * 13. 实践中移动能保证类中的所有状态保持不变,表现良好 42 * 43 * 实例场景: 44 * 1. 函数返回一个局部变量。 45 * 使用移动语义, 不再需要将局部变量拷贝一个副本给返回值。 46 * std::string, std::vector<> 都是具备移动构造函数和移动赋值运算符的,就是为了避免拷贝大量数据。 47 * 48 * 49 * 问-思考: 50 * 1. 移动前后,两个变量的地址相同吗? 51 * 答:不相同。 52 * 2. 一个类同时具备拷贝拷贝构造函数和移动构造函数时, 什么情况下会调用拷贝构造? 而什么情况下会调用移动构造呢? 53 * 答:对于函数结束前返回临时对象的情况。 54 * 若定义了移动构造函数,则不论返回时是否显示使用移动语义,都将调用移动构造函数; 55 * 若未显示定义移动构造函数, 则不论返回时是否显示使用移动语义,都将调用普通的构造函数。 56 * 57 * 对于局部对象构造局部对象的情况。 58 * 若定义了移动构造函数,则显示使用 std::move 时,调用移动构造函数,未显示使用 std::move 则调用普通的构造函数。 59 * 若未定义移动构造函数,则不论是否使用 std::move, 都将调用普通的构造函数。 60 */ 61 62 //! [实例场景] -1 63 int func(){ 64 int a = 10; 65 return std::move(a); 66 } 67 68 std::string func_1(){ 69 std::string _s = "abcdefg"; 70 return std::move(_s); 71 } 72 73 std::vector<int> func_2(){ 74 std::vector<int> _v = {1, 2, 3, 4, 5}; 75 return std::move(_v); 76 } 77 78 std::vector<std::string> func_3(){ 79 std::vector<std::string> _vs = {"abc", "def", "ghi"}; 80 return std::move(_vs); 81 } 82 //! [实例场景] 83 84 85 //! [问-思考] -1 86 std::string think_1(){ 87 std::string _s = "abcdefg"; 88 std::cout<<"In think_1 "<<_s<<" address is "<<&_s<<std::endl; 89 return std::move(_s); 90 } 91 std::string& think_1_1(){ //返回引用编译能通过,运行会崩溃。 问-接龙:为什么移动构造函数不会崩溃 92 std::string _s = "abcdefg"; 93 std::cout<<"In think_1_1 "<<_s<<" address is "<<&_s<<std::endl; 94 return std::move(_s); 95 } 96 //! [问-思考] 97 98 //! [问-思考] -2 99 class MyClass{ 100 public: 101 MyClass(){} 102 MyClass(MyClass & ){ 103 std::cout<<"invoke MyClass(MyClass & other)"<<std::endl; 104 } 105 #ifdef Has_Move 106 MyClass(MyClass &&){ 107 std::cout<<"invoke MyClass(MyClass &&other)"<<std::endl; 108 } 109 #endif 110 }; 111 MyClass think_2(){ 112 std::cout<<"In think_2"<<std::endl; 113 114 MyClass obj; 115 return obj; //可能调用移动构造 116 } 117 MyClass think_2_1(){ 118 std::cout<<"In think_2_1"<<std::endl; 119 120 MyClass obj; 121 return std::move(obj); //可能调用移动构造 122 } 123 124 MyClass& think_2_2(){//返回引用编译能通过,运行会崩溃。 125 std::cout<<"In think_2_2"<<std::endl; 126 127 MyClass obj; 128 return std::move(obj); 129 } 130 //! [问-思考] 131 132 class MyArray{ 133 public: 134 MyArray(char str[], int len):_str(new char[1000]){ 135 std::cout<<"len "<<len<<std::endl; 136 std::copy(str, str+len, _str); 137 } 138 MyArray(MyArray & other):_str(new char[1000]){ 139 std::cout<<"Invoke MyArray(MyArray & other)"<<std::endl; 140 std::copy(other._str, other._str+1000, _str); 141 } 142 MyArray(MyArray &&other):_str(other._str){ 143 std::cout<<"Invoke MyArray(MyArray &&other)"<<std::endl; 144 other._str=0; 145 } 146 147 ~MyArray(){ 148 delete[] _str; 149 } 150 void print(){ 151 std::cout<<_str<<std::endl; 152 } 153 154 private: 155 char * _str; 156 }; 157 158 int main(int argc, char *argv[]) 159 { 160 QCoreApplication a(argc, argv); 161 162 //! [话题] -6 163 //int& _iref = 27; //error: C2440:"初始化":无法从"int"转换为"int&" 164 const int & _ciref = 27; 165 int const& _icref = 27; 166 std::cout<<"_ciref="<<_ciref<<std::endl; 167 std::cout<<"_icref="<<_icref<<std::endl; 168 169 int && _irref = 27; 170 //int && _irref1 = _ciref; //error: C2440:"初始化":无法从"const int"转换为"int&&" 171 //int && _irref2 = _icref; //error: C2440:"初始化":无法从"const int"转换为"int&&" 172 const int & _ciref1 = _irref; //! 特别注意 可以从 int&& 到 const int 173 int const & _icref1 = _irref; //! 特别注意 可以从 int&& 到 int const 174 std::cout<<"_ciref1="<<_ciref<<std::endl; 175 std::cout<<"_icref1="<<_icref<<std::endl; 176 177 std::cout<<"-----------------"<<std::endl<<std::endl; 178 //int const & 和 const int & 这两种类型相同吗? 179 //auto _type1 = typeid(_ciref); 180 //auto _type2 = typeid(_icref); 181 const std::type_info& _type1 = typeid(_ciref); 182 const std::type_info& _type2 = typeid(_icref); 183 std::cout<<"const int & _ciref, _ciref type is "<<_type1.name()<<std::endl; //两种都是 int 类型,有点扑朔迷离 184 std::cout<<"int const& _icref, _icref type is "<<_type2.name()<<std::endl; //两种都是 int 类型,有点扑朔迷离 185 //! [话题] 186 187 std::cout<<"-----------------"<<std::endl<<std::endl; 188 189 //! [实例场景] -1 190 std::cout<<func()<<std::endl; 191 192 std::cout<<func_1()<<std::endl; 193 194 std::vector<int> _v = func_2(); 195 //std::copy(_v.begin(), _v.end(), &std::cout); //error info: C2697 二进制 "=":没有找到接受"int"类型的右操作数的运算符(或没有可接受的转换) 196 for (auto v : _v) 197 std::cout<<v; 198 std::cout<<std::endl; 199 200 std::vector<std::string> _vs = func_3(); 201 //std::copy(_vs.begin(), _vs.end(), &std::cout); //error info: C2697 二进制 “=”:没有找到接受 202 //std::basic_string<char, std::char_traits<char>, std::allocator<char>> 203 //类型的右操作数的运算符(或没有可接受的转换) 204 for (auto s : _vs) 205 std::cout<<s; 206 std::cout<<std::endl; 207 //! [实例场景] 208 209 std::cout<<"-----------------"<<std::endl<<std::endl; 210 211 //! [问-思考] -1 212 std::string _s1 = think_1(); 213 std::cout<<_s1<<" address is "<<&_s1<<std::endl; 214 215 //std::string _s1_1 = think_1_1(); //崩溃了 216 //std::string _s1_1(think_1_1()); //崩溃了 217 //std::cout<<_s1_1<<" address is "<<&_s1_1<<std::endl; 218 219 //! [问-思考] 220 221 //! [问-思考] -2 222 think_2(); 223 think_2_1(); 224 //think_2_2(); 225 226 MyClass _obj; 227 MyClass _obj1(_obj); //普通普通构造 228 MyClass _obj2(std::move(_obj)); //可能调用移动构造 229 //! [问-思考] 230 231 232 std::cout<<"-----------------"<<std::endl<<std::endl; 233 234 char _str[8] = "abcdefg"; 235 MyArray *_myStr = new MyArray(_str, sizeof(_str)); 236 _myStr->print(); 237 238 MyArray *_myStrL = new MyArray(*_myStr); 239 _myStrL->print(); 240 //delete _myStr; _myStr = 0; 241 delete _myStrL; _myStrL = 0; 242 243 MyArray * _myStrR = new MyArray(std::move(*_myStr)); 244 _myStrR->print(); 245 delete _myStr; _myStr = 0; 246 delete _myStrR; _myStrR = 0; 247 //assert(1 == 2); 异常 条件不成立时异常 248 std::cout<<std::endl<<"------finish---"<<std::endl; 249 return a.exec(); 250 }