默认拷贝、深拷贝构造函数

之所以有默认拷贝构造函数、深拷贝构造函数,就是因为对象在作为函数参数时,对象在内存中的数据如何进行传递而产生的。

先看一段带代码:

 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限定符有两个作用:一、防止被复制的对象变样;二、扩大使用范围。

自定义的对象作为参数传递,能用引用就尽量使用引用,能用常量引用的尽量使用常量引用。

posted @ 2012-08-24 16:43  xiaolongxia  阅读(407)  评论(0)    收藏  举报