杂货边角(17):C++11的右值引用和移动语义
2018-02-12 18:33
465 查看
相信绝大多数人,在写C++代码时都会被函数返回值的多次传递弄混淆过。事实上,这涉及到堆栈的运行,对于一个子函数而言,除非程序员显式地调用new操作符,否则函数创建的对象只能在自己的临时栈上,子函数和父函数的临时栈不同,从而这必然存在多次传递的情况。以前在C++标准没有涉及到该问题时,都是编译器自作主张地进行优化,即所谓的RVO(return value optimization),但是编译器提供的RVO也只能在某系局限的场景下发挥作用,可适用性不足。所以还是得靠C++标准来提供相关方面的支持:这便是右值引用和移动语义。
其实将右值引用和左值引用放在一起并可以很好的理解:左值是存在名称,可以取地址的变量等,如全局变量、函数符号等,右值是不存在名称,无法直接取地址的对象,如非引用类型的函数返回值、true、1+2这类对象,C++98此前规定对变量的取引用便是左值引用,而C++11引入的右值引用便是针对右值重复构造存在资源浪费情况设计的,右值引用可以为移动语义(
这其中存在左值引用
其中左值引用和右值运用都必须定义时即初始化,即
而常量左值引用,则是个奇葩,可以使用任何值来初始化,考虑到常量左值引用的语义限制级别最高,所以也是可以理解为什么常量左值可以做到来者不拒。
这也是为什么类中的拷贝构造函数
#include <iostream> #include <type_traits> using namespace std; class HasPtrMem { public: HasPtrMem() : d(new int(3)) { cout << "Construct: " << ++n_cstr << endl; } HasPtrMem(const HasPtrMem & h) : d(new int(*h.d)) { //拷贝构造函数 cout << "Copy construct: " << ++n_cptr << endl; } HasPtrMem(HasPtrMem && h) : d(h.d){ //移动构造函数 ,但在现今的编译器中,其实一般都由编译器默认实现 h.d = nullptr; //将传入的临时对象的指针成员置为空,从而实现鸠占鹊巢 cout << " Move construct:" << ++n_mvtr << endl; } ~HasPtrMem() { delete d; cout << "Destruct: " << ++n_dstr << endl; } int * d; static int n_cstr; static int n_dstr; static int n_cptr; static int n_mvtr; }; int HasPtrMem::n_cstr = 0; int HasPtrMem::n_dstr = 0; int HasPtrMem::n_mvtr = 0; int HasPtrMem::n_cptr = 0; HasPtrMem GetTemp() { HasPtrMem h; cout << "Resource from " << __func__ << ":" << hex << h.d << endl; // ‘<<hex’代表后续数值输出将按照16进制输出 return h; } //HasPtrMem GetTemp() { return HasPtrMem(); } int ReturnRvalue() { int temp = 4; return temp; //函数返回的临时对象,属于右值 } struct Copyable { Copyable() { } Copyable(const Copyable &o ) { cout << "Copied" << endl; } }; Copyable ReturnRvalue1() { return Copyable(); } void AcceptVal(Copyable) { cout << "inside the AccepetVal" << endl; } void AcceptRef(const Copyable &) { cout << "inside the AcceptRef" << endl; } /*=================================================================================== **主角: std::move **=================================================================================*/ class HugeMem { public: HugeMem(int size) : sz(size > 0 ? size : 1){ c = new int[sz];} HugeMem(HugeMem && hm) : sz(hm.sz), c(hm.c) {hm.c = nullptr;cout << "wola" << endl;} //HugeMem(const HugeMem& hm) : sz(hm.sz),c(new int[hm.sz]) { cout << "hola" << endl;} ~HugeMem() { delete [] c; } int * c; int sz; }; class Moveable { public: Moveable():i(new int(3)), h(1024) {} Moveable(Moveable && m): i(m.i),h(move(m.h)) {m.i = nullptr; cout << "fux" << endl;} //启用h(move(m.h)),显式地调用HugeMEM对象的移动构造函数,否则这里m.h将当作左值,调用的是拷贝构造函数 ~Moveable() {delete i; } int* i; HugeMem h; }; Moveable GetTemp1() { Moveable tmp = Moveable(); cout << hex << "Huge Mem from " << __func__ << "@" << tmp.h.c << endl; //Huge Mem from GetTemp xxx return tmp; //tmp作为返回值,将成为右值 } int main() { //HasPtrMem a = GetTemp(); //cout << "Resource from " << __func__ << ":" << hex << a.d << endl; int a; //int && b1 = a; //cannot bind 'int' lvalue to 'int &&' //int & b2 = ReturnRvalue(); //invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' const int & b3 = ReturnRvalue(); cout << " Pass by value: " << endl; AcceptVal(ReturnRvalue1()); //这里存在RVO优化,需要手动关闭编译器的优化,才能看见一次形参的拷贝构造 cout << " Pass by reference: " << endl; AcceptRef(ReturnRvalue1()); /*=================================================================================== **<type_traits> 使用3个模板类:is_rvalue_reference\ is_lvalue_reference\ is_reference **配合decltype等类型推导手段,可以快速判断一个变量是左值引用还是右值引用 **使用is_move_constructible\ is_trivially_move_constructible\ is_nothrow_move_constructible **可以判断一个类型是否可以存在移动语义 **=================================================================================*/ cout << is_rvalue_reference<string &&>::value << endl; //1 string && a1 = "helloworld"; cout << is_rvalue_reference<decltype(a1)>::value << endl; //1 cout << is_move_constructible<int>::value << endl; //1 /*=================================================================================== **主角: std::move **功能: 将一个左值强制转化为右值引用,继而可以通过右值引用使用该值 等同于static_cast<T&&>(lvalue) **库: <utility> **配合decltype等类型推导手段,可以快速判断一个变量是左值引用还是右值引用 **=================================================================================*/ Moveable a2(GetTemp1()); cout << hex << "Huge Mem from " << __func__ << " @ " << a2.h.c << endl; HugeMem a4(100); //HugeMem a5(a4); }
其实将右值引用和左值引用放在一起并可以很好的理解:左值是存在名称,可以取地址的变量等,如全局变量、函数符号等,右值是不存在名称,无法直接取地址的对象,如非引用类型的函数返回值、true、1+2这类对象,C++98此前规定对变量的取引用便是左值引用,而C++11引入的右值引用便是针对右值重复构造存在资源浪费情况设计的,右值引用可以为移动语义(
void func(T&&)即鸠占鹊巢,移动构造等)创造匹配的参数。
这其中存在左值引用
T&,常量左值引用
const T&,右值引用
T&&,常量右值引用
const T&&(仅仅只是出于语义的对称性,其实功能和常量左值引用冲突,不建议使用)。
其中左值引用和右值运用都必须定义时即初始化,即
T & ref = new T,归根结底,因为‘引用’的语义就是需要绑附在具体的对象上,其中左值引用只能使用左值初始化,右值引用也只能使用右值初始化,即
T& lvalueRef = (T)value; //pass T& lvalueRef = ReturnRvalue(); //error T& lvalueRef = (const T)value; //error T&& rvalueRef = (T)value; //error T&& rvalueRef = ReturnRvalue(); //pass
而常量左值引用,则是个奇葩,可以使用任何值来初始化,考虑到常量左值引用的语义限制级别最高,所以也是可以理解为什么常量左值可以做到来者不拒。
const T& conLRef = value; //pass 非常量左值 const T& conLRef = const value; //pass, 常量左值 const T& conLRef = (const)ReturnRvalue(); //pass,常量右值 const T& conLRef = ReturnRvalue(); //pass,非常量右值
这也是为什么类中的拷贝构造函数
type(const type&) {}可以为移动构造函数
type(const type&&) {}兜底的原因。编译器会优先去匹配右值引用类型的构造函数,如果类没有定义,则可以将该次初始化丢给拷贝构造函数来兜底,因为拷贝构造函数的形参是常量左值引用类型,可以接受任何参数。
相关文章推荐
- C++11之右值引用(二):右值引用与移动语义
- 【C++11右值引用与移动语义】——避免深拷贝,优化性能 (附:codeblocks中编译器返回值优化关闭方法)
- C++11之右值引用(二):右值引用与移动语义
- C++11右值引用:移动语义和完美转发
- c++11移动语义右值引用
- C++11特性--右值引用,移动语义,强制移动move()
- C++11之右值引用(二):右值引用与移动语义
- 详解C++11中的右值引用与移动语义
- C++11线程指南(4)--右值引用与移动语义
- C++11移动语义探讨——从临时对象到右值引用
- C++11新特性之0——移动语义、移动构造函数和右值引用
- C++11新特性:移动语义和右值引用
- C++11中右值引用和移动语义
- C++11中的右值引用及move语义编程
- C++11中的Move语义和右值引用
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
- 【转】C++11 标准新特性: 右值引用与转移语义
- 左值引用、右值引用和移动语义
- C++ 的新标准 C++11:右值引用与转移语义
- 左值引用,右值引用以及移动语义