天天看點

什麼情況下要有拷貝構造函數

 拷貝構造函數,經常被稱作X(X&),是一種特殊的構造函數,他由編譯器調用來完成一些基于同一類的其他對象的構件及初始化。它的唯一的一個參數(對象的引用)是不可變的(因為是const型的)。這個函數經常用在函數調用期間于使用者定義類型的值傳遞及傳回。拷貝構造函數要調用基類的拷貝構造函數和成員函數。如果可以的話,它将用常量方式調用,另外,也可以用非常量方式調用。 

在C++中,下面三種對象需要拷貝的情況。是以,拷貝構造函數将會被調用。 

1). 一個對象以值傳遞的方式傳入函數體 

2). 一個對象以值傳遞的方式從函數傳回 

3). 一個對象需要通過另外一個對象進行初始化 

以上的情況需要拷貝構造函數的調用。如果在前兩種情況不使用拷貝構造函數的時候,就會導緻一個指針指向已經被删除的記憶體空間。對于第三種情況來說,初始化和指派的不同含義是構造函數調用的原因。事實上,拷貝構造函數是由普通構造函數和指派操作賦共同實作的。描述拷貝構造函數和指派運算符的異同的參考資料有很多。 

拷貝構造函數不可以改變它所引用的對象,其原因如下:當一個對象以傳遞值的方式傳一個函數的時候,拷貝構造函數自動的被調用來生成函數中的對象。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數将會被調用來拷貝這個對象這樣複制才可以傳入它自己的拷貝構造函數,這會導緻無限循環。 

除了當對象傳入函數的時候被隐式調用以外,拷貝構造函數在對象被函數傳回的時候也同樣的被調用。換句話說,你從函數傳回得到的隻是對象的一份拷貝。但是同樣的,拷貝構造函數被正确的調用了,你不必擔心。 

如果在類中沒有顯式的聲明一個拷貝構造函數,那麼,編譯器會私下裡為你制定一個函數來進行對象之間的位拷貝(bitwise copy)。這個隐含的拷貝構造函數簡單的關聯了所有的類成員。許多作者都會提及這個預設的拷貝構造函數。注意到這個隐式的拷貝構造函數和顯式聲明的拷貝構造函數的不同在于對于成員的關聯方式。顯式聲明的拷貝構造函數關聯的隻是被執行個體化的類成員的預設構造函數除非另外一個構造函數在類初始化或者在構造清單的時候被調用。 

拷貝構造函數是程式更加有效率,因為它不用再構造一個對象的時候改變構造函數的參數清單。設計拷貝構造函數是一個良好的風格,即使是編譯系統提供的幫助你申請記憶體預設拷貝構造函數。事實上,預設拷貝構造函數可以應付許多情況。

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

關于拷貝構造函數和指派運算符

重點:包含動态配置設定成員的類 應提供拷貝構造函數,并重載"="指派操作符。

以下讨論中将用到的例子:

class CExample
{
public:
 CExample(){pBuffer=NULL; nSize=0;}
 ~CExample(){delete pBuffer;}
 void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
 char *pBuffer; //類的對象中包含指針,指向動态配置設定的記憶體資源
 int nSize;
};

      

這個類的主要特點是包含指向其他資源的指針。

pBuffer指向堆中配置設定的一段記憶體空間。

一、拷貝構造函數

int main(int argc, char* argv[])
{
 CExample theObjone;
 theObjone.Init40);
 
 //現在需要另一個對象,需要将他初始化稱對象一的狀态
 CExample theObjtwo=theObjone;
 ...
}
      

語句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。

其完成方式是記憶體拷貝,複制所有成員的值。

完成後,theObjtwo.pBuffer==theObjone.pBuffer。

即它們将指向同樣的地方,指針雖然複制了,但所指向的空間并沒有複制,而是由兩個對象共用了。這樣不符合要求,對象之間不獨立了,并為空間的删除帶來隐患。

是以需要采用必要的手段來避免此類情況。

回顧以下此語句的具體過程:首先建立對象theObjtwo,并調用其構造函數,然後成員被拷貝。

可以在構造函數中添加操作來解決指針成員的問題。

是以C++文法中除了提供預設形式的構造函數外,還規範了另一種特殊的構造函數:拷貝構造函數,上面的語句中,如果類中定義了拷貝構造函數,這對象建立時,調用的将是拷貝構造函數,在拷貝構造函數中,可以根據傳入的變量,複制指針所指向的資源。

拷貝構造函數的格式為:構造函數名(對象的引用)

提供了拷貝構造函數後的CExample類定義為:

class CExample
{
public:
 CExample(){pBuffer=NULL; nSize=0;}
 ~CExample(){delete pBuffer;}
 CExample(const CExample&); //拷貝構造函數
 void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
 char *pBuffer; //類的對象中包含指針,指向動态配置設定的記憶體資源
 int nSize;
};

CExample::CExample(const CExample& RightSides) //拷貝構造函數的定義
{
 nSize=RightSides.nSize; //複制正常成員
 pBuffer=new char[nSize]; //複制指針指向的内容
 memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}

      

這樣,定義新對象,并用已有對象初始化新對象時,CExample(const CExample& RightSides)将被調用,而已有對象用别名RightSides傳給構造函數,以用來作複制。

原則上,應該為所有包含動态配置設定成員的類都提供拷貝構造函數。

拷貝構造函數的另一種調用。

當對象直接作為參數傳給函數時,函數将建立對象的臨時拷貝,這個拷貝過程也将調同拷貝構造函數。

例如

BOOL testfunc(CExample obj);

testfunc(theObjone); //對象直接作為參數。

BOOL testfunc(CExample obj)
{
 //針對obj的操作實際上是針對複制後的臨時拷貝進行的
}

      

還有一種情況,也是與臨時對象有關的

當函數中的局部對象被被傳回給函數調者時,也将建立此局部對象的一個臨時拷貝,拷貝構造函數也将被調用

CTest func()
{
 CTest theTest;
 return theTest
}
      

二、指派符的重載

下面的代碼與上例相似

int main(int argc, char* argv[])
{
 CExample theObjone;
 theObjone.Init(40);
 
 CExample theObjthree;
 theObjthree.Init(60);

 //現在需要一個對象指派操作,被指派對象的原内容被清除,并用右邊對象的内容填充。
 theObjthree=theObjone;
 return 0;
}

      

也用到了"="号,但與"一、"中的例子并不同,"一、"的例子中,"="在對象聲明語句中,表示初始化。更多時候,這種初始化也可用括号表示。

例如 CExample theObjone(theObjtwo);

而本例子中,"="表示指派操作。将對象theObjone的内容複制到對象theObjthree;,這其中涉及到對象theObjthree原有内容的丢棄,新内容的複制。

但"="的預設操作隻是将成員變量的值相應複制。舊的值被自然丢棄。

由于對象内包含指針,将造成不良後果:指針的值被丢棄了,但指針指向的内容并未釋放。指針的值被複制了,但指針所指内容并未複制。

是以,包含動态配置設定成員的類除提供拷貝構造函數外,還應該考慮重載"="指派操作符号。

類定義變為:

class CExample
{
 ...
 CExample(const CExample&); //拷貝構造函數
 CExample& operator = (const CExample&); //指派符重載
 ...
};

      
//指派操作符重載
CExample & CExample::operator = (const CExample& RightSides)
{
 nSize=RightSides.nSize; //複制正常成員
 char *temp=new char[nSize]; //複制指針指向的内容 
 memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));

 delete []pBuffer; //删除原指針指向内容  (将删除操作放在後面,避免X=X特殊情況下,内容的丢失)
 pBuffer=temp;   //建立新指向
 return *this
}
      

三、拷貝構造函數使用指派運算符重載的代碼。

CExample::CExample(const CExample& RightSides)
{
 pBuffer=NULL;
 *this=RightSides  //調用重載後的"="
}      
/      
拷貝構造函數本身是一個構造函數,但通過輸入另一個同類對象的值來初始化。   
   拷貝複制函數(應該是拷貝指派函數吧?)調用時,對象早已構造出來了,這時隻是用另一個對象的值來給賦給它.2者可以說是不相關吧,如果說要有點關系,一般是把“用另一個對象的值來初始化”這部分封閉成一個assign函數,然後在這2個函數裡分别調用,以消除2個函數中的相同代碼,并使得“用另一個對象的值來初始化”這個操作沒有差異性。   
    簡單的說,一般情況下(注意是一般情況),拷貝構造函數=構造函數+拷貝指派函數,并消除後者的重複操作。      

繼續閱讀