拷貝建構函式的作用

linlinlinxi007發表於2010-02-02

如果你沒有為你的程式新增一個拷貝建構函式,那麼編譯器也會向你的程式新增一個預設的拷貝建構函式.在一般情況下使用編譯器的預設拷貝建構函式就可以了,但是如果你的類的成員資料有指標變數的存在,那麼你最好自己構造一個拷貝建構函式,否則你的程式可能隱藏著許多錯誤.
請看下面這一個程式,存在多少錯誤?

#include "stdafx.h"
#include "iostream.h"
#include "string.h"
class String
{
int nLen;
char *cArray;
public:
String(){nLen=0; cArray=NULL;}
~String(){delete []cArray; nLen=0;}
String(const char *str)
{
int len=strlen(str)+1;
cArray=new char[len];
strcpy(cArray,str);
nLen=len;
}
void operator=(const char *str)
{
if(nLen)
delete []cArray;
int len=strlen(str)+1;
cArray=new char[len];
strcpy(cArray,str);
nLen=len;
}
char *GetBuffer()const{return cArray;}
//----------------------------------------------------
String  operator=(String p)
{
if(nLen)
delete []cArray;
strcpy(p.cArray,p.GetBuffer());
int len=strlen(p.GetBuffer())+1;
cArray=new char[len];
nLen=len;
strcpy(cArray,p.GetBuffer());
return *this;
}
//---------------------------------------------------
};

int main(int argc, char* argv[])
{
String str("meihaofeng");
char *p=str.GetBuffer();
cout<<p<<endl;
str="mei";
p=str.GetBuffer();
cout<<p<<endl;
String s="hello!";
str=s;//這裡開始出錯
p=str.GetBuffer();
cout<<p<<endl;
return 0;
}
初看也許你會覺得沒有什麼錯誤啊,但你將這段完整的程式拿去執行一下(編譯連結是能通過的),啊!那些錯誤有些驚人.下面讓我們來分析一下,出現那些錯誤的原因.
加粗的那個函式是出錯的根本,主要就是針對這個函式進行分析.在主函式是從“str=s;”這一句開始出錯的。執行str=s對應String  operator=(String p)函式上。首先將“s”作為引數拷貝給String  operator=(String p)的形參p。在這個過程中,s的兩個成員資料
nLen和cArray也相應的拷貝給p的nLen和cArray.但是cArray是一個指標,拷貝之後p.cArray的值和s.cArray的值是相等的,可以知道這兩個指標變數都指向同一片記憶體區域,這一個記憶體區域就是儲存“hello!”的記憶體區域。當String  operator=(String p)這個函式執行完後,由於物件p超出了它的作用範圍,所以呼叫物件p的解構函式,在解構函式中釋放cArray所指向的記憶體,此時釋放cArray所指向的的記憶體相當於釋放p.cArray或主函式中s.cArray所指向的內在區域。釋放完後返回*this物件,注意,返回的*this這個物件將拷貝給主函式的一個臨時的物件,之後就釋放這個臨時物件的記憶體。在將*this拷貝給一個臨時物件的過程如同將s作為實參傳給形參p一樣,使臨時物件的兩個資料成員nLen和cArray與(*this)物件的nLen和cArray相等,同理(*this).cArray與臨時物件的cArray指向現一片記憶體區域。在釋放這個臨時物件時先呼叫這它的解構函式,從上面的程式可以知道這個*this代表主函式的一個物件str,所以在解構函式中就釋放str.cArray所指向的記憶體。 
    從String  operator=(String p)中由前面的分析可知,主函式中s.cArray以及str.cArray所指向的記憶體已經被釋放了。返回到主函式通過p=str.GetBuffer();cout<<p<<endl;列印str.cArray的值也就列印出了錯誤的值。當主函式中str與s兩個物件的生存週期結束後,這兩個物件的解構函式會被再次呼叫。在呼叫解構函式時,又會釋放s.cArray和str.cArray所指向的記憶體,由於在String  operator=(String p)函式中這兩個指標所指向的記憶體區域已經被釋放了,所以在主函式中再次釋放就會出錯。這些就是程式中隱藏著的錯誤。

    最好的解決方法就是不要編譯器新增的拷貝建構函式,而是自己手動新增一個,並在這個拷貝建構函式中執行一此操作。下面的程式修補了上面程式的錯誤,程式如下:
#include "stdafx.h"
#include "iostream.h"
#include "string.h"


class String
{  
int nLen;
char *cArray;
public:
String(){nLen=0; cArray=NULL;}
~String()
{
cout<<"~String()"<<endl;
delete []cArray;
nLen=0;
}
String(const char *str)
{
int len=strlen(str)+1;
cArray=new char[len];
strcpy(cArray,str);
nLen=len;
}
void operator=(const char *str)
{
if(nLen)
delete []cArray;
int len=strlen(str)+1;
cArray=new char[len];
strcpy(cArray,str);
nLen=len;
}
char *GetBuffer()const{return cArray;}
//----------------------------------------------------
String(const String & s)//自己新增的拷貝建構函式。
{
        cout<<"拷貝建構函式"<<endl;
        char *temp;
        temp=new char[s.nLen];
        strcpy(temp,s.GetBuffer());
        cArray=new char[s.nLen];
        strcpy(cArray,temp);
        delete temp;
}
//----------------------------------------------------
String  operator=(String p)
{
if(nLen)
delete []cArray;
strcpy(p.cArray,p.GetBuffer());
int len=strlen(p.GetBuffer())+1;
cArray=new char[len];
nLen=len;
strcpy(cArray,p.GetBuffer());
return *this;
}
//---------------------------------------------------
};

int main(int argc, char* argv[])
{
String str("meihaofeng");
char *p=str.GetBuffer();
cout<<p<<endl;
str="mei";
p=str.GetBuffer();
cout<<p<<endl;
String s="hello!";
str=s;
p=str.GetBuffer();
cout<<p<<endl;
return 0;
}
    在拷貝建構函式中要為目標物件中的指標分配空間,並讓指標指向分配的空間。並將源物件的指標所指向的空間裡的值複製到分配的空間中。注意這裡的目標物件,對應到上面的程式“str=s”中,str即為目標物件,s為源物件。在String  operator=(String p)返回時臨時物件為目標物件,*this為源物件。
下面是程式的執行結果:
---------------------------------
meihaofeng
mei
拷貝建構函式
拷貝建構函式
~String()
~String()
hello!
~String()
~String()
---------------------------------
從執行結果為可以看到拷貝建構函式被呼叫了兩次,第一次是,執行str=s時,將實參s拷貝給String  operator=(String p)函式的形參p.
第二次是,在String  operator=(String p)返回裡,將*this拷貝給一個臨時物件。解構函式被呼叫了四次,從程式中也不難分析出是那四次.
第一、二是String  operator=(String p)中發生的。第三、四次是在主函式發生的。

相關文章