在C++中,下面三種對象需要拷貝的情況。因此,拷貝構造函數將會被調用。
1). 一個對象以值傳遞的方式傳入函數體
2). 一個對象以值傳遞的方式從函數返回
3). 一個對象需要通過另外一個對象進行初始化
以上的情況需要拷貝構造函數的調用。如果在前兩種情況不使用拷貝構造函數的時候,就會導致一個指針指向已經被刪除的內存空間。對於第三種情況來說,初始化和賦值的不同含義是構造函數調用的原因。事實上,拷貝構造函數是由普通構造函數和賦值操作賦共同實現的。描述拷貝構造函數和賦值運算符的異同的參考資料有很多。
拷貝構造函數不可以改變它所引用的對象,其原因如下:當一個對象以傳遞值的方式傳一個函數的時候,拷貝構造函數自動的被調用來生成函數中的對象。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數將會被調用來拷貝這個對象這樣複製纔可以傳入它自己的拷貝構造函數,這會導致無限循環。
除了當對象傳入函數的時候被隱式調用以外,拷貝構造函數在對象被函數返回的時候也同樣的被調用。換句話說,你從函數返回得到的只是對象的一份拷貝。但是同樣的,拷貝構造函數被正確的調用了,你不必擔心。
如果在類中沒有顯式的聲明一個拷貝構造函數,那麼,編譯器會私下裏爲你制定一個函數來進行對象之間的位拷貝(bitwise copy)。這個隱含的拷貝構造函數簡單的關聯了所有的類成員。許多作者都會提及這個默認的拷貝構造函數。注意到這個隱式的拷貝構造函數和顯式聲明的拷貝構造函數的不同在於對於成員的關聯方式。顯式聲明的拷貝構造函數關聯的只是被實例化的類成員的缺省構造函數除非另外一個構造函數在類初始化或者在構造列表的時候被調用。
拷貝構造函數是程序更加有效率,因爲它不用再構造一個對象的時候改變構造函數的參數列表。設計拷貝構造函數是一個良好的風格,即使是編譯系統提供的幫助你申請內存默認拷貝構造函數。事實上,默認拷貝構造函數可以應付許多情況。
對一個簡單變量的初始化方法是用一個常量或變量初始化另一個變量,例如:
int m = 80;
int n = m;
我們已經會用構造函數初始化對象,那麼我們能不能象簡單變量的初始化一樣,直接用一個對象來初始化另一個對象呢?答案是肯定的。我們以前面定義的Point類爲例:
Point pt1(15, 25);
Point pt2 = pt1;
後一個語句也可以寫成:
Point pt2( pt1);
它是用pt1初始化pt2,此時,pt2各個成員的值與pt1各個成員的值相同,也就是說,pt1各個成員的值被複制到pt2相應的成員當中。在這個初始化過程當中,實際上調用了一個複製構造函數。當我們沒有顯式定義一個複製構造函數時,編譯器會隱式定義一個缺省的複製構造函數,它是一個內聯的、公有的成員,它具有下面的原型形式:
Point:: Point (const Point &);
可見,複製構造函數與構造函數的不同之處在於形參,前者的形參是Point對象的引用,其功能是將一個對象的每一個成員複製到另一個對象對應的成員當中。
雖然沒有必要,我們也可以爲Point類顯式定義一個複製構造函數:
Point:: Point (const Point &pt)
{
xVal=pt. xVal;
yVal=pt. yVal;
}
如果一個類中有指針成員,使用缺省的複製構造函數初始化對象就會出現問題。爲了說明存在的問題,我們假定對象A與對象B是相同的類,有一個指針成員,指向對象C。當用對象B初始化對象A時,缺省的複製構造函數將B中每一個成員的值複製到A的對應的成員當中,但並沒有複製對象C。也就是說,對象A和對象B中的指針成員均指向對象C,實際上,我們希望對象C也被複制,得到C的對象副本D。否則,當對象A和B銷燬時,會對對象C的內存區重複釋放,而導致錯誤。爲了使對象C也被複制,就必須顯式定義複製構造函數。下面我們以string類爲例說明,如何定義這個複製構造函數。
例10-11 | |
class String { public: String(); //構造函數 String(const String &s); //複製構造函數 ~String(); //析構函數 // 接口函數 private: { str = new char[strlen(s.str) + 1]; strcpy(str, s.str); } |
|
我們也常用無名對象初始化另一個對象,例如:
Point pt = Point(10, 20);
類名直接調用構造函數就生成了一個無名對象,上式用左邊的無名對象初始化右邊的pt對象。
構造函數被調用通常發生在以下三種情況,第一種情況就是我們上面看到的:用一個對象初始化另一個對象時;第二種情況是當對象作函數參數,實參傳給形參時;第三種情況是程序運行過程中創建其它臨時對象時。下面我們再舉一個例子,就第二種情況和第三種情況進行說明:
Point foo(Point pt)
{
…
return pt;
}
void main()
{
Point pt1 = Point(10, 20);
Point pt2;
…
pt2=foo(pt);
…
}
在main函數中調用foo函數時,實參pt傳給形參pt,將實參pt複製給形參pt,要調用複製構造函數,當函數foo返回時,要創建一個pt的臨時對象,此時也要調用複製構造函數。
缺省的複製構造函數
在類的定義中,如果沒有顯式定義複製構造函數,C++編譯器會自動地定義一個缺省的複製構造函數。下面是使用複製構造函數的一個例子:
例10-12 | |
#include <iostream.h> #include <string.h> class withCC { public: withCC(){} withCC(const withCC&) { cout<<"withCC(withCC&)"<<endl; } }; class woCC class composite void main() |
|
類composite既含有withCC類的成員對象又含有woCC類的成員對象,它使用無參的構造函數創建withCC類的對象WITHCC(注意內嵌的對象WOCC的初始化方法)。
在main()函數中,語句:
composite c2 = c;
通過對象C初始化對象c2,缺省的複製構造函數被調用。
最好的方法是創建自己的複製構造函數而不要指望編譯器創建,這樣就能保證程序在我們自己的控制之下。