如果一个类有外部资源那么
一函数返回这个类实例化的对象,一个临时对象给一个新对象拷贝构造或者赋值的时候就会产生如下图所示的情况:
1,程序首先申请和tmp对象占用的外部资源相同大小的外部资源,然后再将tmp的外部资源逐一拷贝给临时变量中。
2,然后函数结束后tmp的生命周期结束,执行tmp的析构函数释放掉外部资源。
3,程序为了拷贝构造t2向系统申请和临时变量相同大小的外部资源,然后逐一将临时变量的数据拷贝给t2中
4,然后语句执行完毕,临时变量的生命周期结束,执行临时变量的析构函数释放临时变量所占用的外部资源。
总结:上诉过程中就是普通的拷贝构造和等号运算符重载之后的效果。那么我们可以很直观的看到中间临时变量作为中间人,执行俩次的拷贝和释放资源的操作,那么显然我们是否可以将第一步的资源直接给临时变量,再将临时变量中的资源直接给t2呢,这样显然可以节省不少的开销,答案是肯定的。
前提一个类有外部资源。且以这个类写成的对象进行拷贝构造或者赋值的时候。
关于拷贝构造就需要申请和被拷贝构造相同大小的空间,然后再将被拷贝构造的对象的内容一份一份的再拷贝给新的对象。
关于赋值的说明和拷贝构造类似。
那么问题的关键就在于,如果我们拷贝构造和等号运算符的是临时对象,且我们占有了很大的外部资源。那么我们这样一份一份拷贝是很浪费开销的。
因为临时对象的生命周期就只在当前语句中,出了语句它就被析构了,那么有没有一种办法可以直接把临时变量的占有的外部资源给新产生的对象呢。
这里我们就可以使用右值引用。
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
mptr = new int[10000];
cout << "Test()" << endl;
}
~Test()
{
delete[]mptr;
cout << "~Test()" << endl;
}
Test(const Test &src)
{
cout << "Test(const Test&)" << endl;
/*mptr = new int[10000];
for (int i = 0; i < 10000; ++i)
{
mptr[i] = src.mptr[i];
}*/
}
//main栈临时对象 tmp
Test(Test &&src) // 带右值引用参数的拷贝构造
{
cout << "Test(Test&&)" << endl;
mptr = src.mptr;
src.mptr = nullptr;
}
void operator=(const Test &src)
{
cout << "operator=" << endl;
if (this == &src)
return;
delete[]mptr;
mptr = new int[10000];
for (int i = 0; i < 10000; ++i)
{
mptr[i] = src.mptr[i];
}
}
void operator=(Test &&src) // 带右值引用参数的赋值函数
{
cout << "operator=(Test&&)" << endl;
if (this == &src)
return;
delete[]mptr;
mptr = src.mptr;
src.mptr = nullptr;
}
private:
int *mptr;
};
Test GetTestObject(Test &t)
{
Test tmp;
return tmp;
}
Test GetTestObject1(Test &t)
{
//int val = t.getData();
Test tmp;
return tmp;
}
int main()
{
Test t1;
Test t2;
//Test t2 = GetTestObject1(t1);
t2 = GetTestObject1(t1);
return 0;
}
这里结果为:
可见我们如果把拷贝构造函数和等号运算符函数改为右值引用就可以解决把临时变量占有的外部资源直接给新生成的对象的问题。右值引用避免了程序的开销,增加了程序运行的效率。
第一行到最后一行的解析
t1的构造
t2的构造
tmp的构造
tmp给临时变量的右值引用的拷贝构造
tmp的析构
右值引用的等号运算符重载(临时变量给t2赋值)
临时变量的析构
t2析构
t1析构