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;
}
}
};
5.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重新指向新申请的空间。析构所有对象这样整个程序结束。
由此看来,深拷贝在一些情况下严重干扰堆空间,对其不停的申请和释放。
5.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值都相同,说明他们一直指向同一块内存空间。