默认拷贝、深拷贝构造函数
之所以有默认拷贝构造函数、深拷贝构造函数,就是因为对象在作为函数参数时,对象在内存中的数据如何进行传递而产生的。
先看一段带代码:
1 #include <iostream> 2 using namespace std; 3 4 class Person 5 { 6 char *pName; 7 public: 8 Person(char *pN) 9 { 10 cout<<"Constructing "<<pN<<endl; 11 pName = new char[strlen(pN)+1]; 12 if(pName) 13 strcpy(pName,pN); 14 } 15 ~Person() 16 { 17 cout<<"Destructing "<<pName<<endl; 18 if (pName) 19 { 20 delete[] pName; 21 } 22 23 } 24 }; 25 int main() 26 { 27 Person p1("Randy"); 28 Person p2(p1); 29 30 return 0; 31 }
这段代码结果为:
Constructing Randy
Destructing Randy
Destructing 葺葺葺葺葺
Press any key to continue
结果发现,没有第二次输出“Constructing...”,也就是创建p2时并没有调用Person的构造函数。而且析构函数的表现也不正常了。原因是对象进行了C++的默认拷贝构造拷贝。而默认拷贝构造仅仅拷贝了对象本体。如下图
于是先系够p2时,将存有Randy的空间现行释放了,轮到p1析构时,Randy已经不复存在,因此访问该空间的操作变得不可预料的怪异。
整个流程大概如下:
1、创建对象p1,调用构造函数Person;
2、将对象p1赋值给对象p2,会调用默认的拷贝构造函数
3、对象p2进行析构函数的调用
4、对象p1进行析构函数的调用 ; 改程序就是这里出现问题,对已经释放的堆空间,在进行释放,肯定会引起错误!!!
反汇编主要代码(VC6.0的debug模式下,一下没有特别说明都是该环境下进行)
1 Person p1("Randy"); 2 0040119D push offset string "Randy" (0043201c) 3 004011A2 lea ecx,[ebp-10h] 4 004011A5 call @ILT+130(Person::Person) (00401087) ;压入对象p1的首地址,然后调用类的构造函数
5 004011AA mov dword ptr [ebp-4],0 ;作用于内对象的个数 6 28: Person p2(p1); 7 004011B1 mov eax,dword ptr [ebp-10h] ;这两句就是默认的拷贝构造函数,就是简单的进行复制,因为对象只有一个指针变量,直接使用寄存器进行转存就行了 8 004011B4 mov dword ptr [ebp-14h],eax ;先将p1对象内的指针赋值给eax,然后再放入p2对象的内存地址中,这就完成了拷贝
9 29: 10 30: return 0; 11 004011B7 mov dword ptr [ebp-18h],0 12 004011BE lea ecx,[ebp-14h] 13 004011C1 call @ILT+10(Person::~Person) (0040100f) ;析构函数跟构造函数调用的顺序相反 ,因而先进行p2对象的析构函数的调用 14 004011C6 mov dword ptr [ebp-4],0FFFFFFFFh 15 004011CD lea ecx,[ebp-10h] 16 004011D0 call @ILT+10(Person::~Person) (0040100f) ;在进行p1对象的析构函数的调用 17 004011D5 mov eax,dword ptr [ebp-18h]
当对象在内存中的数据较多时(我尝试了一下,当大于4以后),默认的拷贝如下形式
1 class Person 2 { 3 char *pName; 4 int a; 5 int b; 6 int c; 7 int d; 8 ......... 9 10 };
汇编代码如下:
1 32: Person p2(p1); 2 004011B1 mov ecx,5 3 004011B6 lea esi,[ebp-20h] ; 存放的是对象p1的首地址 4 004011B9 lea edi,[ebp-34h] ; 存放的是对象p2的首地址 5 004011BC rep movs dword ptr [edi],dword ptr [esi]
为了弥补以上出现的问题,进而提出了一种解决方案,深拷贝构造函数,即在类中定义一个深拷贝构造函数,在该函数中,程序员手动进行堆空间数据的拷贝,以防止出现上述问题
1 #include <iostream> 2 using namespace std; 3 class Person 4 { 5 char *pName; 6 public: 7 8 Person(const Person& p) ;深拷贝构造函数 9 { 10 cout <<"copying "<<p.pName<<"into its own block\n"; 11 pName=new char [strlen(p.pName)+1]; 12 if (pName) 13 strcpy(pName,p.pName); 14 } 15 16 ....... 17 18 } ; 19 int main() 20 { 21 Person p1("Randy"); 22 Person p2(p1); 23 }
深拷贝构造函数(自定义拷贝构造函数)中,程序员自己进行数据,以及堆空间数据的拷贝,如下
因而不会产生默认拷贝构造函数产生的问题。
其主要反汇编代码如下:
1 33: Person p2(p1); 2 004011C1 lea eax,[ebp-10h] ;先将p1对象的首地址压入栈中 3 004011C4 push eax 4 004011C5 lea ecx,[ebp-14h] ;在将对象p2的this指针压入栈中 5 004011C8 call @ILT+30(Person::Person) (00401023) ; 这里会调用类中的深拷贝函数
注意:深拷贝构造函数名也是类名,它是构造函数的重载,一旦自定了拷贝构造函数,默认的拷贝构造函数就不再起作用了。
深拷贝构造函数的参数必须是类对象的常量引用:
Person(const Person & s);
吟哦日对象复制的语意本身尚处于当前定义当中,则对象复制操作调用的拷贝构造函数在哪里?!所以只能是引用或者指针。(指针影响复制的语法 如:Person p2(*p1);)
const限定符有两个作用:一、防止被复制的对象变样;二、扩大使用范围。
自定义的对象作为参数传递,能用引用就尽量使用引用,能用常量引用的尽量使用常量引用。