1、右值引用与函数重载
class Int { int value; public: Int(int x = 0) :value(x) { cout << "create " << this << endl; } ~Int() { cout << "destroy " << this << endl; } Int(const Int& it) :value(it.value) { cout << &it << " copy " << this << endl; } Int& operator=(const Int& it) { if (this != &it) { value = it.value; } cout << &it << " operator= " << this << endl; return *this; } void PrintValue()const { cout<<"value: "<<value<<endl; } Int& SetValue(int x) { value=x; return *this; } }; //void func(Int a){}//会和void fun(Int&& c)形成二义性,原因是无名对象即可以赋值给对象也可以赋值给右值引用 void func(Int& a) { cout<<"lvalue_reference"<<endl; } void func(const Int& b) { cout<<"lvalue_const_reference"<<endl; } void func(Int&& c) { cout<<"rvalue_reference"<<endl; } int main() { Int x(10); const Int y(20); func(x);//优先匹配func(Int& a),没有就匹配func(const Int& b) func(y);//只能匹配func(const Int& b) func(Int(30));//优先匹配func(Int&& c),没有就匹配func(const Int& b) Int z(10); func((Int&&)z);//调用func(Int&& c) func((const Int&&)z);//调用func(const Int& b) return 0; }
2、右值引用优化性能,避免深拷贝
class MyString { private: char* str; public: MyString(const char* p=nullptr):str(nullptr) { if(p!=nullptr) { int n=strlen(p)+1; str=new char[n]; strcpy_s(str,n,p); } cout<<"Create MyString"<<this<<endl; } ~MyString() { if(str!=nullptr) { delete[] str; str=nullptr; } str=nullptr; } MyString(const MyString& st):str(nullptr)//深拷贝 { if(st.str!=nullptr) { int n=strlen(st.str)+1; str=new char[n]; strcpy_s(str,n,st.str); } cout<<"Copy Create MyString"<<this<<endl; } MyString& operator=(const MyString& st)//深赋值 { if(this==&st||str==st.str) { return *this; } delete[] str;//str为空时也可以进行delete,因为delete调用free,在free中会进行判空 int n=strlen(st.str)+1; str=new char[n]; strcpy_s(str,n,st.str); cout<<this<<"operator= MyString"<<&st<<endl; return *this; } void Print()const { if(str!=nullptr) { cout<<str<<endl; } } };
2.1使用深拷贝和深复制会对堆区空间造成巨大影响
MyString func(const char* p) { MyString tmp(p); return tmp; } int main() { MyString s1("hello"); s1=func("helloworld"); s1.Print(); return 0; }
在main函数中能看见的字符串"hello"和"helloworld"均存放在.data区,p指针指向的也是.data区的"helloworld"。运行时进入主函数,首先创建s1对象,在堆区空间申请空间存放字符串"hello",s1.str指向该堆区空间。
再调用func()函数,进入func()函数先创建tmp对象,在堆区申请空间存放字符串"helloworld",tmp.str指向该堆区空间。
返回时,使用tmp对象构建将亡值对象xvalue,同样的,在堆区申请和tmp所申请大小相同的空间,将字符串"helloworld"赋值过去进行存放,xvalue.str指向该堆区空间。需要注意的是xvalue这个将亡值对象是在main栈帧中创建的,而不是func()函数栈帧中。创建完将亡值对象后,func()函数结束销毁tmp对象,将其所指向的堆区空间进行释放。
再回到主函数,将该将亡值对象赋值给s1对象时,调用赋值函数。首先申请和将亡值对象申请大小相同的空间,将字符串"helloworld"赋值过去进行存放,释放s1对象开始指向的堆区空间,之后再将s1.str重新指向新申请的空间。析构所有对象这样整个程序结束。
由此看来,深拷贝在一些情况下严重干扰堆空间,对其不停的申请和释放。
2.2使用移动拷贝构造和移动赋值提升性能(移动资源)
//移动拷贝构造 MyString(MyString&& st):str(nullptr) { str=st.str; st.str=nullptr; cout<<"Move Copy Create"<<endl; } //移动赋值 MyString& operator=(MyString&& st) { if(this==&st)return *this; delete[] str; str=st.str; st.str=nullptr; cout<<"Move Operator= "<<endl; return *this; }
加入移动拷贝构造和移动赋值后再执行下面代码:
MyString func(const char* p) { MyString tmp(p); return tmp; } int main() { MyString s1("hello"); s1=func("helloworld"); s1.Print(); return 0; }
同样的,运行时进入主函数,首先创建s1对象,在堆区空间申请空间存放字符串"hello",s1.str指向该堆区空间。
再调用func()函数,进入func()函数先创建tmp对象,在堆区申请空间存放字符串"helloworld",tmp.str指向该堆区空间。
返回时tmp为左值对象,但系统认为在func函数中定义的局部对象tmp其生存期只在该函数中,使用return返回tmp对象时,认为是要将tmp的资源进行移动,就会将其看成是将亡值。(系统“作弊”)
所以在func函数返回前会调用移动拷贝构造函数将tmp对象的资源移动给创建的xvalue将亡值,func函数结束,析构tmp对象。
回到main函数时,将将亡值xvalue赋值给s1对象时,调用移动赋值函数,将xvalue的资源再次移动给s1对象,赋值结束后xvalue将亡值对象消亡,打印s1的内容,析构所有对象程序结束。
在这个过程中,并没有多次反复的申请空间和释放空间,而是将申请的空间在多个对象之间进行移动,这样就使得程序的性能提高。
如果将str设置为公有,即可在func和主函数中打印str的值,会发现tmp和func("helloworld")以及赋值完成后的s1的str值都相同,说明他们一直指向同一块内存空间。