拷贝构造函数与移动构造函数
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
拷贝构造函数与移动构造函数
⼀、拷贝构造函数
当类没有定义拷贝构造函数的时候,编译器会默认提供⼀个,这个拷贝函数是浅拷贝。
如果该类中含有指针,可能会发⽣内存泄漏,见下⾯的例⼦:
class Test
{
public:
int *p;
Test(){ p=new int; };
~Test(){ delete p; };
};
void main()
{
Test t1;
Test t2(t1);
Test t3 = t1;
}
t1、t2、t3的成员变量p指向的是同⼀块内存,程序结束后会出现重复释放的问题。
为了解决这个问题,可以⾃定义拷贝构造函数:
class Test
{
public:
int *p;
Test(const Test &t)
{
p = new int (*(t.p));
}
Test(){ p=new int; };
~Test(){ delete p; };
};
⼆、右值引⽤
除了上述的解决⽅法,还可以使⽤C++11的【右值引⽤】新特性来解决,⽽且可以提⾼程序的性能,减少内存开销。
为了引出左值引⽤的概念,先来复习左值和右值
1.左值和右值
int a = 3 + 4 ;
上⾯的式⼦中,变量 a 就是左值,右边的表达式会⽣成⼀个临时变量存放 (3+4) 的值,这个变量称之为右值。
有两种⽅式可以判断:
(1)只能放在等号(=)右侧的即为右值,可以放在左侧的为左值
int a = 10 ;
10 = a ; //错误
(2)左值可以取地址,⽽右值不允许:
int a = 3 + 4 ;
int * b = & a ; //ok
b = & (3+4) ; //错误
2.右值引⽤
使⽤⽅法如下,b就是对右值 (3+4) 的引⽤。
int && b = 3 + 4 ;
先看下下⾯的左值引⽤:
int a = 0 ;
int &b = 4 ; //错误!
int &b = a ; //左值引⽤
如上例所⽰,左值引⽤只能对左值进⾏别名引⽤,⽆法引⽤右值
于是C++11增加了右值引⽤,使⽤ && 表⽰(和逻辑运算中的”且“⼀致)。
int a = 0 ;
int b = 1 ;
int && c = a+c ; //右值引⽤
int && c = 3 ; //右值引⽤
int && c = 3 +4 ; //右值引⽤
int && c = a ; //错误!
注意不能直接右值引⽤左值,C++提供了⼀个函数std::move()函数,可以将左值变成右值:
string str1 = "aa" ;
string&& str2 = std::move( str1 ); //ok
3.右值引⽤的应⽤场景
(1)案例:
还是回到之前的例⼦:
class Test
{
public:
int *p;
Test(const Test &t)
{
p = new int (*(t.p)); cout<<"copy construct"<<endl;
}
Test(){ p=new int; cout<<"construct"<<endl; };
~Test(){ delete p; cout<<"destruct"<<endl; };
};
Test getTest()
{
return Test();
}
void main()
{
{
Test t = getTest();
}
}
使⽤vs2012运⾏,结果为:
construct //执⾏ Test()
destruct //销毁 t
但需要注意的是,这是vs编译器对拷贝构造函数优化后的结果。
禁⽌优化,结果为:
construct //执⾏ Test()
copy construct //执⾏ return Test()
destruct //销毁 Test() 产⽣的匿名对象
copy construct //执⾏ t = getTest()
destruct //销毁 getTest() 返回的临时对象
destruct //销毁 t
可以看到,进⾏了两次的深拷贝,对于对内存要求不⾼、本例这种占内存⽐较⼩的类Test⽽⾔(申请的堆空间⼩),可以接受。
但如果临时对象中的指针成员申请了⼤量的堆空间,那将严重影响程序的执⾏效率。
C++11为了解决这⼀问题(深拷贝占⽤⼤量空间),引⼊移动构造函数。
(2)移动构造函数
所谓的移动,就是将其他的内存资源,“移为⼰有”,这些资源通常是临时对象,⽐如上⽂所叙的右值。
修改如下(增加⼀个移动构造函数):
class Test
{
public:
int *p;
Test(Test &&t) //移动构造函数
{
p = t.p;
t.p = nullptr;//将临时对象的指针赋值为空
cout<<"copy construct"<<endl;
}
Test(const Test &t) //拷贝构造函数
{
p = new int (*(t.p));
cout<<"move construct"<<endl;
}
Test(){ p=new int; cout<<"construct"<<endl; };
~Test(){ delete p; cout<<"disconstruct"<<endl; };
};
Test getTest()
{
return Test();
}
void main()
{
{
Test t = getTest();
}
}
禁⽌vs优化,结果为:
construct //执⾏ Test()
move construct//执⾏ return Test()
destruct //销毁 Test() 产⽣的匿名对象
move construct//执⾏ t = getTest()
destruct //销毁 getTest() 返回的临时对象
destruct //销毁 t
可以看到,定义了移动构造函数后,临时对象的创建使⽤移动构造函数创建,如下,没有在堆上创建对象,减少了开销。
Test(Test &&t) //移动构造函数
{
p = t.p;
t.p = nullptr;//将临时对象的指针赋值为空
cout<<"copy construct"<<endl;
}
那么问题来了,什么时候调⽤移动构造函数,什么时候调⽤拷贝构造函数呢?将在后⾯的⽂章中分析。