赞
踩
当类没有定义拷贝构造函数的时候,编译器会默认提供一个,这个拷贝函数是浅拷贝。
如果该类中含有指针,可能会发生内存泄漏,见下面的例子:
- 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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。