轉(zhuǎn)自此處,貌似博主也是轉(zhuǎn)別人的
也許很多C++的初學(xué)者都知道什么是構(gòu)造函數(shù),但是對(duì)復(fù)制構(gòu)造函數(shù)(copy constructor)卻還很陌生。對(duì)于我來(lái)說(shuō),在寫(xiě)代碼的時(shí)候能用得上復(fù)制構(gòu)造函數(shù)的機(jī)會(huì)并不多,不過(guò)這并不說(shuō)明復(fù)制構(gòu)造函數(shù)沒(méi)什么用,其實(shí)復(fù)制構(gòu)造函數(shù)能解決一些我們常常會(huì)忽略的問(wèn)題。
為了說(shuō)明復(fù)制構(gòu)造函數(shù)作用,我先說(shuō)說(shuō)我們?cè)诰幊虝r(shí)會(huì)遇到的一些問(wèn)題。
對(duì)于C++中的函數(shù),我們應(yīng)該很熟悉了,因?yàn)槠匠=?jīng)常使用;對(duì)于類(lèi)的對(duì)象,我們也很熟悉,因?yàn)槲覀円步?jīng)常寫(xiě)各種各樣的類(lèi),使用各種各樣的對(duì)象;對(duì)于指針的
操作,我們也不陌生吧?嗯,如果你還不了解上面三個(gè)概念的話,我想這篇文章不太適合你,不過(guò)看看也無(wú)礙^_^。我們經(jīng)常使用函數(shù),傳遞過(guò)各種各樣的參數(shù)給
函數(shù),不過(guò)把對(duì)象(注意是對(duì)象,而不是對(duì)象的指針或?qū)ο蟮囊茫┊?dāng)作參數(shù)傳給函數(shù)的情況我們應(yīng)該比較少遇見(jiàn)吧,而且這個(gè)對(duì)象的構(gòu)造函數(shù)還涉及到一些內(nèi)存分配的操作。嗯,這樣會(huì)有什么問(wèn)題呢?
把參數(shù)傳遞給函數(shù)有三種方法,一種是值傳遞,一種是傳地址,還有一種
是傳引用。前者與后兩者不同的地方在于:當(dāng)使用值傳遞的時(shí)候,會(huì)在函數(shù)里面生成傳遞參數(shù)的一個(gè)副本,這個(gè)副本的內(nèi)容是按位從原始參數(shù)那里復(fù)制過(guò)來(lái)的,兩者
的內(nèi)容是相同的。當(dāng)原始參數(shù)是一個(gè)類(lèi)的對(duì)象時(shí),它也會(huì)產(chǎn)生一個(gè)對(duì)象的副本,不過(guò)在這里要注意。一般對(duì)象產(chǎn)生時(shí)都會(huì)觸發(fā)構(gòu)造函數(shù)的執(zhí)行,但是在產(chǎn)生對(duì)象的副
本時(shí)卻不會(huì)這樣,這時(shí)執(zhí)行的是對(duì)象的復(fù)制構(gòu)造函數(shù)。為什么會(huì)這樣?嗯,一般的構(gòu)造函數(shù)都是會(huì)完成一些成員屬性初始化的工作,在對(duì)象傳遞給某一函數(shù)之前,對(duì)
象的一些屬性可能已經(jīng)被改變了,如果在產(chǎn)生對(duì)象副本的時(shí)候再執(zhí)行對(duì)象的構(gòu)造函數(shù),那么這個(gè)對(duì)象的屬性又再恢復(fù)到原始狀態(tài),這并不是我們想要的。所以在產(chǎn)生對(duì)象副本的時(shí)候,構(gòu)造函數(shù)不會(huì)被執(zhí)行,被執(zhí)行的是一個(gè)默認(rèn)的構(gòu)造函數(shù)。當(dāng)函數(shù)執(zhí)行完畢要返回的時(shí)候,對(duì)象副本會(huì)執(zhí)行析構(gòu)函數(shù),
如果你的析構(gòu)函數(shù)是空的話,
就不會(huì)發(fā)生什么問(wèn)題,但一般的析構(gòu)函數(shù)都是要完成一些清理工作,如釋放指針?biāo)赶虻膬?nèi)存空間。這時(shí)候問(wèn)題就可能要出現(xiàn)了。假如你在構(gòu)造函數(shù)里面為一個(gè)指針
變量分配了內(nèi)存,在析構(gòu)函數(shù)里面釋放分配給這個(gè)指針?biāo)赶虻膬?nèi)存空間,那么在把對(duì)象傳遞給函數(shù)至函數(shù)結(jié)束返回這一過(guò)程會(huì)發(fā)生什么事情呢?首先有一個(gè)對(duì)象的
副本產(chǎn)生了,這個(gè)副本也有一個(gè)指針,它和原始對(duì)象的指針是指向同塊內(nèi)存空間的。函數(shù)返回時(shí),對(duì)象的析構(gòu)函數(shù)被執(zhí)行了,即釋放了對(duì)象副本里面指針?biāo)赶虻膬?nèi)
存空間,但是這個(gè)內(nèi)存空間對(duì)原始對(duì)象還是有用的啊,就程序本身而言,這是一個(gè)嚴(yán)重的錯(cuò)誤。然而錯(cuò)誤還沒(méi)結(jié)束,當(dāng)原始對(duì)象也被銷(xiāo)毀的時(shí)候,析構(gòu)函數(shù)再次執(zhí)
行,對(duì)同一塊系統(tǒng)動(dòng)態(tài)分配的內(nèi)存空間釋放兩次是一個(gè)未知的操作,將會(huì)產(chǎn)生嚴(yán)重的錯(cuò)誤。
上面說(shuō)的就是我們會(huì)遇到的問(wèn)題。解決問(wèn)題的方法是什么呢?首先我們想
到的是不要以傳值的方式來(lái)傳遞參數(shù),我們可以用傳地址或傳引用。沒(méi)錯(cuò),這樣的確可以避免上面的情況,而且在允許的情況下,傳地址或傳引用是最好的方法,但
這并不適合所有的情況,有時(shí)我們不希望在函數(shù)里面的一些操作會(huì)影響到函數(shù)外部的變量。那要怎么辦呢?可以利用復(fù)制構(gòu)造函數(shù)來(lái)解決這一問(wèn)題。復(fù)制構(gòu)造函數(shù)就是在產(chǎn)生對(duì)象副本的時(shí)候執(zhí)行的,我們可以定義自己的復(fù)制構(gòu)造函數(shù)。在復(fù)制構(gòu)造函數(shù)里面我們申請(qǐng)一個(gè)新的內(nèi)存空間來(lái)保存構(gòu)造函數(shù)里面的那個(gè)指針?biāo)赶虻膬?nèi)容。這樣在執(zhí)行對(duì)象副本的析構(gòu)函數(shù)時(shí),釋放的就是復(fù)制構(gòu)造函數(shù)里面所申請(qǐng)的那個(gè)內(nèi)存空間。
除了將對(duì)象傳遞給函數(shù)時(shí)會(huì)存在以上問(wèn)題,還有一種情況也會(huì)存在以上問(wèn)題,就是當(dāng)函數(shù)返回對(duì)象時(shí),會(huì)產(chǎn)生一個(gè)臨時(shí)對(duì)象,這個(gè)臨時(shí)對(duì)象和對(duì)象的副本性質(zhì)差不多。
拷貝構(gòu)造函數(shù),經(jīng)常被稱作X(X&),是一種特殊的構(gòu)造函數(shù),他由編譯器調(diào)用來(lái)完成一些基于同一類(lèi)的其他對(duì)象的構(gòu)件及初始化。它的唯一的一個(gè)參數(shù)(對(duì)象的引用)是不可變的(因?yàn)槭莄onst型的)。這個(gè)函數(shù)經(jīng)常用在函數(shù)調(diào)用期間于用戶定義類(lèi)型的值傳遞及返回。拷貝構(gòu)造函數(shù)要調(diào)用基類(lèi)的拷貝構(gòu)造函數(shù)和成員函數(shù)。如果可以的話,它將用常量方式調(diào)用,另外,也可以用非常量方式調(diào)用。
在C++中,下面三種對(duì)象需要拷貝的情況。因此,拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用。
1). 一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體
2). 一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回
3). 一個(gè)對(duì)象需要通過(guò)另外一個(gè)對(duì)象進(jìn)行初始化
以
上的情況需要拷貝構(gòu)造函數(shù)的調(diào)用。如果在前兩種情況不使用拷貝構(gòu)造函數(shù)的時(shí)候,就會(huì)導(dǎo)致一個(gè)指針指向已經(jīng)被刪除的內(nèi)存空間。對(duì)于第三種情況來(lái)說(shuō),初始化和賦值的不同含義是構(gòu)造函數(shù)調(diào)用的原因。事實(shí)上,拷貝構(gòu)造函數(shù)是由普通構(gòu)造函數(shù)和賦值操作符共同實(shí)現(xiàn)的。描述拷貝構(gòu)造函數(shù)和賦值運(yùn)算符的異同的參考資料有很多。
拷貝構(gòu)造函數(shù)不可以改變它所引用的對(duì)象,其原因如下:當(dāng)一個(gè)對(duì)象以傳遞值的方式傳一個(gè)函數(shù)的時(shí)候,拷貝構(gòu)造函數(shù)自動(dòng)的被調(diào)用來(lái)生成函數(shù)中的對(duì)象。如果一個(gè)對(duì)象是被傳入自己的拷貝構(gòu)造函數(shù),它的拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用來(lái)拷貝這個(gè)對(duì)象這樣復(fù)制才可以傳入它自己的拷貝構(gòu)造函數(shù),這會(huì)導(dǎo)致無(wú)限循
環(huán)。
除了當(dāng)對(duì)象傳入函數(shù)的時(shí)候被隱式調(diào)用以外,拷貝構(gòu)造函數(shù)在對(duì)象被函數(shù)返回的時(shí)候也同樣的被調(diào)用。換句話說(shuō),你從函數(shù)返回得到的只是對(duì)象的一份拷貝。但是同樣的,拷貝構(gòu)造函數(shù)被正確的調(diào)用了,你不必?fù)?dān)心。
如果在類(lèi)中沒(méi)有顯式的聲明一個(gè)拷貝構(gòu)造函數(shù),那么,編譯器會(huì)私下里為你制定一個(gè)函數(shù)來(lái)進(jìn)行對(duì)象之間的位拷貝(bitwise
copy)。這個(gè)隱含的拷貝構(gòu)造函數(shù)簡(jiǎn)單的關(guān)聯(lián)了所有的類(lèi)成員。許多作者都會(huì)提及這個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)。注意到這個(gè)隱式的拷貝構(gòu)造函數(shù)和顯式聲明的拷貝構(gòu)造函數(shù)的不同在于對(duì)于成員的關(guān)聯(lián)方式。顯式聲明的拷貝構(gòu)造函數(shù)關(guān)聯(lián)的只是被實(shí)例化的類(lèi)成員的缺省構(gòu)造函數(shù)除非另外一個(gè)構(gòu)造函數(shù)在類(lèi)初始化或者在構(gòu)造列表的時(shí)候被調(diào)用。
拷貝構(gòu)造函數(shù)使程序更加有效率,因?yàn)樗挥迷贅?gòu)造一個(gè)對(duì)象的時(shí)候改變構(gòu)造函數(shù)的參數(shù)列表。設(shè)計(jì)拷貝構(gòu)造函數(shù)是一個(gè)良好的風(fēng)格,即使是編譯系統(tǒng)提供的幫助你申請(qǐng)內(nèi)存默認(rèn)拷貝構(gòu)造函數(shù)。事實(shí)上,默認(rèn)拷貝構(gòu)造函數(shù)可以應(yīng)付許多情況。
附另外一篇關(guān)于復(fù)制構(gòu)造函數(shù)的文章:
對(duì)一個(gè)簡(jiǎn)單變量的初始化方法是用一個(gè)常量或變量初始化另一個(gè)變量,例如:
int m = 80;
int n = m;
我們已經(jīng)會(huì)用構(gòu)造函數(shù)初始化對(duì)象,那么我們能不能象簡(jiǎn)單變量的初始化一樣,直接用一個(gè)對(duì)象來(lái)初始化另一個(gè)對(duì)象呢?答案是肯定的。我們以前面定義的Point類(lèi)為例:
Point pt1(15, 25);
Point pt2 = pt1;
后一個(gè)語(yǔ)句也可以寫(xiě)成:
Point pt2( pt1);
它
是用pt1初始化pt2,此時(shí),pt2各個(gè)成員的值與pt1各個(gè)成員的值相同,也就是說(shuō),pt1各個(gè)成員的值被復(fù)制到pt2相應(yīng)的成員當(dāng)中。在這個(gè)初始化
過(guò)程當(dāng)中,實(shí)際上調(diào)用了一個(gè)復(fù)制構(gòu)造函數(shù)。當(dāng)我們沒(méi)有顯式定義一個(gè)復(fù)制構(gòu)造函數(shù)時(shí),編譯器會(huì)隱式定義一個(gè)缺省的復(fù)制構(gòu)造函數(shù),它是一個(gè)內(nèi)聯(lián)的、公有的成
員,它具有下面的原型形式:
Point:: Point (const Point &);
可見(jiàn),復(fù)制構(gòu)造函數(shù)與構(gòu)造函數(shù)的不同之處在于形參,前者的形參是Point對(duì)象的引用,其功能是將一個(gè)對(duì)象的每一個(gè)成員復(fù)制到另一個(gè)對(duì)象對(duì)應(yīng)的成員當(dāng)中。
雖然沒(méi)有必要,我們也可以為Point類(lèi)顯式定義一個(gè)復(fù)制構(gòu)造函數(shù):
Point:: Point (const Point &pt)
{
xVal=pt. xVal;
yVal=pt. yVal;
}
如果一個(gè)類(lèi)中有指針成員,使用缺省的復(fù)制構(gòu)造函數(shù)初始化對(duì)象就會(huì)出現(xiàn)問(wèn)題。為了說(shuō)明存在的問(wèn)題,我們假定對(duì)象A與對(duì)象B是相同的類(lèi),有一個(gè)指針成員,指
向?qū)ο驝。當(dāng)用對(duì)象B初始化對(duì)象A時(shí),缺省的復(fù)制構(gòu)造函數(shù)將B中每一個(gè)成員的值復(fù)制到A的對(duì)應(yīng)的成員當(dāng)中,但并沒(méi)有復(fù)制對(duì)象C。也就是說(shuō),對(duì)象A和對(duì)象B
中的指針成員均指向?qū)ο驝,實(shí)際上,我們希望對(duì)象C也被復(fù)制,得到C的對(duì)象副本D。否則,當(dāng)對(duì)象A和B銷(xiāo)毀時(shí),會(huì)對(duì)對(duì)象C的內(nèi)存區(qū)重復(fù)釋放,而導(dǎo)致錯(cuò)誤。
為了使對(duì)象C也被復(fù)制,就必須顯式定義復(fù)制構(gòu)造函數(shù)。下面我們以string類(lèi)為例說(shuō)明,如何定義這個(gè)復(fù)制構(gòu)造函數(shù)。
 |
例題
|
例10-11 |
|
1 class String 2 { 3 public: 4 String(); //構(gòu)造函數(shù) 5 String(const String &s); //復(fù)制構(gòu)造函數(shù) 6 ~String(); //析構(gòu)函數(shù) 7 8 // 接口函數(shù) 9 void set(char const *data); 10 char const *get(void); 11 12 private: 13 char *str; //數(shù)據(jù)成員ptr指向分配的字符串 14 }; 15 String ::String(const String &s) 16 { 17 str = new char[strlen(s.str) + 1]; 18 strcpy(str, s.str); 19 }
|
 |
我們也常用無(wú)名對(duì)象初始化另一個(gè)對(duì)象,例如:
Point pt = Point(10, 20);
類(lèi)名直接調(diào)用構(gòu)造函數(shù)就生成了一個(gè)無(wú)名對(duì)象,上式用左邊的無(wú)名對(duì)象初始化右邊的pt對(duì)象。
構(gòu)造函數(shù)被調(diào)用通常發(fā)生在以下三種情況,第一種情況就是我們上面看到的:用一個(gè)對(duì)象初始化另一個(gè)對(duì)象時(shí);第二種情況是當(dāng)對(duì)象作函數(shù)參數(shù),實(shí)參傳給形參時(shí);第三種情況是程序運(yùn)行過(guò)程中創(chuàng)建其它臨時(shí)對(duì)象時(shí)。下面我們?cè)倥e一個(gè)例子,就第二種情況和第三種情況進(jìn)行說(shuō)明:
Point foo(Point pt)
{
…
return pt;
}
void main()
{
Point pt1 = Point(10, 20);
Point pt2;
…
pt2=foo(pt);
…
}
在main函數(shù)中調(diào)用foo函數(shù)時(shí),實(shí)參pt傳給形參pt,將實(shí)參pt復(fù)制給形參pt,要調(diào)用復(fù)制構(gòu)造函數(shù),當(dāng)函數(shù)foo返回時(shí),要?jiǎng)?chuàng)建一個(gè)pt的臨時(shí)對(duì)象,此時(shí)也要調(diào)用復(fù)制構(gòu)造函數(shù)。
缺省的復(fù)制構(gòu)造函數(shù)
在類(lèi)的定義中,如果沒(méi)有顯式定義復(fù)制構(gòu)造函數(shù),C++編譯器會(huì)自動(dòng)地定義一個(gè)缺省的復(fù)制構(gòu)造函數(shù)。下面是使用復(fù)制構(gòu)造函數(shù)的一個(gè)例子:
 |
例題
|
例10-12 |
|
1 #include <iostream.h> 2 #include <string.h> 3 class withCC 4 { 5 public: 6 withCC(){} 7 withCC(const withCC&) 8 { 9 cout<<"withCC(withCC&)"<<endl; 10 } 11 }; 12 13 class woCC 14 { 15 enum{bsz = 100}; 16 char buf[bsz]; 17 public: 18 woCC(const char* msg = 0) 19 { 20 memset(buf, 0, bsz); 21 if(msg) strncpy(buf, msg, bsz); 22 } 23 void print(const char* msg = 0)const 24 { 25 if(msg) cout<<msg<<":"; 26 cout<<buf<<endl; 27 } 28 }; 29 30 class composite 31 { 32 withCC WITHCC; 33 woCC WOCC; 34 public: 35 composite() : WOCC("composite()"){} 36 void print(const char* msg = 0) 37 { 38 WOCC.print(msg); 39 } 40 }; 41 42 void main() 43 { 44 composite c; 45 c.print("contents of c"); 46 cout<<"calling composite copy-constructor"<<endl; 47 composite c2 = c; 48 c2.print("contents of c2"); 49 }
|
 |
類(lèi)withCC有一個(gè)復(fù)制構(gòu)造函數(shù),類(lèi)woCC和類(lèi)composite都沒(méi)有顯式定義復(fù)制構(gòu)造函數(shù)。如果在類(lèi)中沒(méi)有顯式定義復(fù)制構(gòu)造函數(shù),則編譯器將自動(dòng)地創(chuàng)建一個(gè)缺省的構(gòu)造函數(shù)。不過(guò)在這種情況下,這個(gè)構(gòu)造函數(shù)什么也不作。
類(lèi)composite既含有withCC類(lèi)的成員對(duì)象又含有woCC類(lèi)的成員對(duì)象,它使用無(wú)參的構(gòu)造函數(shù)創(chuàng)建withCC類(lèi)的對(duì)象WITHCC(注意內(nèi)嵌的對(duì)象WOCC的初始化方法)。
在main()函數(shù)中,語(yǔ)句:
composite c2 = c;
通過(guò)對(duì)象C初始化對(duì)象c2,缺省的復(fù)制構(gòu)造函數(shù)被調(diào)用。
最好的方法是創(chuàng)建自己的復(fù)制構(gòu)造函數(shù)而不要指望編譯器創(chuàng)建,這樣就能保證程序在我們自己的控制之下。