[轉(zhuǎn)載]http://www.csdn.net/develop/article/22/22033.shtm

構(gòu)造函數(shù)(中)

?

三、復(fù)制構(gòu)造函數(shù)

?

??? 1.存在的理由

?

??? 廚 師做烹飪的時(shí)候總要往鍋里加入各式各樣的調(diào)料,調(diào)料的種類、數(shù)量在相當(dāng)大的程度上就決定了菜肴的口感;經(jīng)驗(yàn)豐富的廚師總是擅長于根據(jù)顧客的品味差異來調(diào)節(jié) 調(diào)料的投入,以迎合顧客的喜好。我們?cè)谂谥茖?duì)象的時(shí)候亦如此:通過重載不同具有參數(shù)表的構(gòu)造函數(shù),可以按我們的需要對(duì)新創(chuàng)建的對(duì)象進(jìn)行初始化。譬如,對(duì)于 復(fù)數(shù)類Complex,我們可以在創(chuàng)建時(shí)指定實(shí)部、虛部,它通過“投入”的兩個(gè)double參數(shù)來實(shí)現(xiàn);而對(duì)于整型數(shù)組類IntArray,我們亦可以在 構(gòu)造函數(shù)中“投入”一個(gè)int值作為數(shù)組對(duì)象的初始大小,如果需要,我們還可以再“撒進(jìn)”一個(gè)內(nèi)部數(shù)組作為數(shù)組各元素的初始值,等等。嗯,總之,就是說我 們可以通過向類的構(gòu)造函數(shù)中傳入各種參數(shù),從而在對(duì)象創(chuàng)建之初便根據(jù)需要可對(duì)它進(jìn)行初步的“調(diào)制”。假使我們已經(jīng)做好了一道菜,呃,不,是已經(jīng)有了一個(gè)對(duì) 象,比如說,一個(gè)復(fù)數(shù)類Complex的對(duì)象c,現(xiàn)在我們想新創(chuàng)建一個(gè)復(fù)數(shù)對(duì)象d,使之與c完全相等,應(yīng)該怎么辦?噢,有了上節(jié)的經(jīng)驗(yàn),我知道你會(huì)興沖沖 地走到電腦,敲出類似于下面的代碼:

?

??? Complex d(c.getRe(), c.getIm());???? // getRe()與getIm()分別返回復(fù)數(shù)的實(shí)、虛部

?

??? 很 好,它可以正確的工作,不是嗎?不過,再回過頭兩欣賞幾眼之后,你是否和我一樣開始覺得似乎這樣有些冗長?而且,應(yīng)該看到復(fù)數(shù)類是一個(gè)比較簡單的抽象數(shù)據(jù) 類型,它提供的公有接口可以讓我們?cè)L問到它所有的私有成員變量,所以我們才得以獲得c的所有的“隱私”數(shù)據(jù)來對(duì)d進(jìn)行初始化;但有相當(dāng)多的類,是不能也不 該訪問到它所有的私有對(duì)象的,退一步說,就算可以完全訪問,我們的構(gòu)造函數(shù)參數(shù)表也未必總能精細(xì)地、一絲不茍地刻畫對(duì)象,例如對(duì)于IntArray類的某 個(gè)對(duì)象,例如a,可能它會(huì)包含一千個(gè)元素,但我們不可能寫

?

??? IntArray copy_of_a(a.size(), a[0], a[1], a[2], ..., a[999]);? // 足夠詳細(xì)的刻畫,同時(shí)也足夠愚蠢

?

??? 唔, 看來我們錯(cuò)就錯(cuò)在試圖用數(shù)量有限的輔助參數(shù)來刻畫我們的對(duì)象,而它往往是不合適的。要想以對(duì)象a來100%地確定對(duì)象b,就必須讓對(duì)象b掌握對(duì)象a的 100%的信息,也就是說,構(gòu)造函數(shù)中所傳入的參數(shù)應(yīng)該包含對(duì)象a的100%的信息。誰包含了“對(duì)象a的100%的信息”呢?看上去似乎是一道難題,哦, 我們似乎被前面各個(gè)雜亂的參數(shù)表弄得頭暈?zāi)X脹,但我們不應(yīng)該忘卻從簡單的方面考慮問題的法則,包含“對(duì)象a的100%的信息”的家伙,不應(yīng)該是一群數(shù)量巨 大的變量,不應(yīng)該是一組令人恐懼的參數(shù),它應(yīng)該就是...就是對(duì)象a本身。

??? 啊哈,真是廢話。別管這么多,我們現(xiàn)在要讓復(fù)數(shù)d初始化之后立刻等于復(fù)數(shù)c,就可以寫

?

??? Complex d(c);??? // 嗯...完美的表示

?

??? “等等...”你也許會(huì)提醒說,“你好像還沒有定義與之對(duì)應(yīng)的構(gòu)造函數(shù)。”

??? 這 是一個(gè)非常好心的提醒,不過我可以帶著愉悅的心情告訴你:C++對(duì)于“以與自己同類型的對(duì)象為作為唯一參數(shù)傳入的構(gòu)造函數(shù)”已經(jīng)做了默認(rèn)的定義,對(duì)于這個(gè) 默認(rèn)的構(gòu)造函數(shù)所創(chuàng)建的對(duì)象,將與作為參數(shù)傳入的對(duì)象具有完全相同的內(nèi)容:實(shí)際上,這個(gè)過程一般是以“位拷貝”的方式進(jìn)行的,即是將參數(shù)對(duì)象所占的那一塊 內(nèi)存的內(nèi)容“一股腦”地拷貝到新創(chuàng)建的對(duì)象所處的內(nèi)存區(qū)塊中,這樣做的結(jié)果,便使得新創(chuàng)建的對(duì)象與參數(shù)對(duì)象內(nèi)有完全相同的成員變量值,無論是公有的還是私 有的,因而,我們也就得以以一個(gè)已存在的對(duì)象來初始化一個(gè)新的對(duì)象,當(dāng)然,兩者應(yīng)該是同一類型的。

??? 你也許會(huì)對(duì)“為何C++要默認(rèn)提供這樣一個(gè)構(gòu)造函數(shù)”感興趣。實(shí)質(zhì)上,不僅僅是上面的“聲明對(duì)象并初始化”的例子要使用到這個(gè)特性,在一些更普遍的情況、同時(shí)也是我們所不注意的情況中,必須要使用到這個(gè)功能,例如:進(jìn)行值傳遞的函數(shù)調(diào)用。考慮下面的代碼片斷:

?

??? void swap(int a, int b)

??? {

??????? int temp = a;

??????? a = b;

??????? b = temp;

??? }

?

??? int main()

??? {

????? ?int x = 1, y = 2;

?????? swap(x, y);??? // 噢...

?????? ... // others

?????? return 0;

??? }

?

??? 問 題:執(zhí)行了swap(x, y)之后,x, y的值分別是多少?毫無疑問,當(dāng)然是x==1, y==2,因?yàn)閳?zhí)行swap時(shí),交換的并不是x,y變量的值,而只是它們的一個(gè)副本。我們可以從某個(gè)角度認(rèn)為:在調(diào)用swap函數(shù)時(shí),將會(huì)“新創(chuàng)建”兩個(gè) 變量a, b,而a, b分別以x, y的值進(jìn)行初始化,像這樣:

?

??? int a(x), b(y);

??? ...? // 此后的操作都與x, y無關(guān)了

?

??? 同樣,假如有這樣一個(gè)函數(shù):

??? void swap(Complex a, Complex b)

??? {

??????? ...

??? }

??? 在出現(xiàn)諸如swap(p, q)這樣的調(diào)用時(shí),也會(huì)對(duì)p, q進(jìn)行復(fù)制操作,類似于:Complex a(p), b(q);

??? 與參數(shù)的傳入一樣,函數(shù)在返回時(shí)(如果有返回的話),只要不是返回某個(gè)引用,則也會(huì)出現(xiàn)類似的復(fù)制操作(這里不考慮某些省卻復(fù)制步驟的編譯優(yōu)化)。

??? 所以,假如系統(tǒng)不會(huì)為我們默認(rèn)定義這樣的特殊的構(gòu)造函數(shù),我們將不能定義任何出現(xiàn)Complex類型的參數(shù),或者返回值為Complex類型的函數(shù),而這簡直是沒有道理的,至少,看上去不是那么好令人接受。

??? 由于這樣的構(gòu)造函數(shù)可以看作是從一個(gè)既有的同型對(duì)象中“復(fù)制”創(chuàng)建出一個(gè)新對(duì)象,所以也把這個(gè)構(gòu)造函數(shù)稱為復(fù)制構(gòu)造函數(shù),或拷貝構(gòu)造函數(shù)(copy constructor).實(shí)質(zhì)上它與其它的構(gòu)造函數(shù)沒有區(qū)別,只是:

??? 1.如果沒有明確給出,系統(tǒng)會(huì)默認(rèn)提供一個(gè)復(fù)制構(gòu)造函數(shù)(類似于不帶參數(shù)的構(gòu)造函數(shù),以及析構(gòu)函數(shù));

??? 2.進(jìn)行值傳遞的函數(shù)調(diào)用或返回時(shí),系統(tǒng)將使用此構(gòu)造函數(shù)(相對(duì)于其它的通常只存在人為調(diào)用的構(gòu)造函數(shù))。

?

??? 噫,以上便是有關(guān)復(fù)制構(gòu)造函數(shù)的講解。時(shí)間不早,該休息了。朋友,祝您晚安,下次見...

??? 哦, 不不不,弄錯(cuò)了,沒有這么簡單。我們...我們才剛剛開始...唔,至少我還得在睡前把這篇文章打完,而且下面似乎內(nèi)容不少,總之,還有相當(dāng)多的因素要考 慮。嗯,你可能說,C++提供的默認(rèn)復(fù)制構(gòu)造函數(shù)不是已經(jīng)盡職盡責(zé)地、一個(gè)bit不漏地為我們將對(duì)象原原本本地克隆過去么,那么所得的新對(duì)象肯定與被復(fù)制 對(duì)象完全一樣,還有什么要考慮的?

??? 問 題就出在對(duì)于“完全一樣”的理解上。我們說兩個(gè)三角形完全一樣,是指它們具有對(duì)應(yīng)相同的邊、角;我們說兩個(gè)復(fù)數(shù)完全一樣,是指它們具有對(duì)應(yīng)相等的實(shí)部、虛 部;我們說兩個(gè)文件完全一樣,是指它們的內(nèi)容一絲不茍地相同...那我們說某個(gè)類型的兩個(gè)對(duì)象完全一樣,當(dāng)然,從樸素的意義上說,應(yīng)該是指這兩個(gè)對(duì)象具有 相同的成員,包括成員函數(shù)與成員變量;不過,同型對(duì)象當(dāng)然有相同的成員函數(shù),所以更確切地說,完全相同即是具有相同的成員變量。

??? 但C ++中的類的對(duì)象并非、也不應(yīng)該僅被視為綣縮于內(nèi)存中某個(gè)角落的一系列二進(jìn)制數(shù)據(jù),那是機(jī)器的觀點(diǎn)。事實(shí)上,對(duì)象作為現(xiàn)實(shí)模型的一個(gè)描述,它本身應(yīng)該具有 一定的抽象而來意義。一個(gè)Orange類的對(duì)象,在編程時(shí)也許我們更多的時(shí)候會(huì)有意無意地將其作為一個(gè)活生生的桔子進(jìn)行考慮,而非一撮二進(jìn)制流。C++的 面向?qū)ο蟪橄笫刮覀兏玫乜坍嫭F(xiàn)實(shí)模型,并進(jìn)行正確的、恰當(dāng)?shù)氖褂谩R虼耍?dāng)我們說兩個(gè)對(duì)象“完全相等”,基于抽象的思想,我們所指的,或者說所要求的, 只是對(duì)象所代表的現(xiàn)實(shí)模型的意義上的相等,至于在二進(jìn)制層面的實(shí)現(xiàn)上,它們內(nèi)部的0101是否一樣,倒不是我們最關(guān)心的,當(dāng)然,可能在許多情況下,兩者是 等同的,但也不盡然。譬如兩個(gè)IntArray數(shù)組對(duì)象,當(dāng)然,它們各包含了一個(gè)整型指針作為私有成員,以存放數(shù)組所處內(nèi)存空間的首地址。但我們說這兩個(gè) 數(shù)組對(duì)象相同,更多意義上指的是它們含有相同的維數(shù)、尺寸,以及對(duì)應(yīng)相等的元素,至于存放首地址的指針值,盡管它們?cè)诖蠖鄶?shù)情況下都不相等,但這并不妨礙 我們認(rèn)為兩個(gè)數(shù)組相同。在這個(gè)例子中,“對(duì)象”所代表的概念上的意義與它在機(jī)器層面的意義出現(xiàn)了分岐,程序語言的任務(wù)之一就在于提供更恰當(dāng)?shù)某橄螅钩绦? 具有某種吻合于現(xiàn)實(shí)模型的條理性。所以兩者出現(xiàn)分岐之時(shí),既然我們使用的是具有相當(dāng)抽象能力的高級(jí)語言,就應(yīng)當(dāng)傾向于尊重現(xiàn)實(shí)的概念,而不是返樸歸真。

??? 說 了這么多,現(xiàn)在該回過頭來看一看默認(rèn)復(fù)制構(gòu)造函數(shù)所采用的“完全照搬”復(fù)制模式將有可能帶來怎樣的“概念層面”與“機(jī)器層面”的矛盾。剛才我們已經(jīng)抓到了 IntArray類的尾巴,現(xiàn)在來看看,假如使用代表著“機(jī)器層面理解”的默認(rèn)復(fù)制函數(shù)對(duì)它進(jìn)行復(fù)制構(gòu)造,會(huì)發(fā)生什么事情。嗯,沒錯(cuò),新創(chuàng)建的 IntArray對(duì)象--不妨稱為b--將具有與原對(duì)象--不妨稱為a--完全相同的成員變量,當(dāng)然也包括用于存儲(chǔ)數(shù)組內(nèi)存空間首地址的指針成員,也就是 說,a與b實(shí)際上是“共享”了同一塊內(nèi)存以存放“它們”的數(shù)據(jù)。從此,如果我改變a數(shù)組中的某個(gè)元素的值,則b中相應(yīng)元素也會(huì)改變,反之亦然--我們的程 序鬧鬼了。更為嚴(yán)重的是,如果我們?cè)贗ntArray的析構(gòu)函數(shù)中加入了釋放數(shù)組內(nèi)存的代碼(幾乎所有的容器都會(huì)這樣做),那么由于有多個(gè)對(duì)象共享同一塊 內(nèi)存,這塊可憐的內(nèi)存將會(huì)被釋放多次,事實(shí)上當(dāng)?shù)诙蝸砼R時(shí)我們的程序很可能就已經(jīng)崩潰了,這種情況在值傳遞調(diào)用函數(shù)時(shí)最為典型。

??? 我 們概念意義上對(duì)“數(shù)組復(fù)制”的理解是產(chǎn)生一個(gè)新數(shù)組,使之與原數(shù)組的元素均對(duì)應(yīng)相同,而絕不是什么共享內(nèi)存的鬼把戲。但我們忠厚的C++編譯器可不了解這 些,畢竟它只是機(jī)器,所以也只會(huì)盡職盡責(zé)地為我們施行位拷貝。這樣一來,我們就有義務(wù)把我們對(duì)概念本身的理解告訴C++,當(dāng)然,這個(gè)途徑就是顯示地定義一 個(gè)復(fù)制構(gòu)造函數(shù)。

?

??? 2.關(guān)于聲明的討論

?

??? 首 先,以IntArray類為例,我們來看看復(fù)制構(gòu)造函數(shù)的聲明應(yīng)該是什么樣子:構(gòu)造函數(shù)是沒有返回值的,復(fù)制構(gòu)造函數(shù)當(dāng)然也不例外,因此我們只須考慮參 數(shù)。復(fù)制構(gòu)造函數(shù)只有一個(gè)參數(shù),由于在創(chuàng)建時(shí)傳入的是同種類型的對(duì)象,所以一個(gè)很自然的想法是將該類型的對(duì)象作為參數(shù),像這樣:

?

??? IntArray(IntArray a);

?

??? 不 幸的是,即使是這樣樸實(shí)無華的聲明也隱含了一個(gè)微妙的錯(cuò)誤,呵,我們來看看:當(dāng)某個(gè)時(shí)候需要以一個(gè)IntArray對(duì)象的值來為一個(gè)新對(duì)象進(jìn)行初始化時(shí), 當(dāng)然,編譯器會(huì)在各個(gè)重載的構(gòu)造函數(shù)版本(如果有多個(gè)的話)搜尋,它找到的這個(gè)版本,發(fā)現(xiàn)聲明參數(shù)與傳入的對(duì)象一致,因此該構(gòu)造函數(shù)將會(huì)被調(diào)用。目前為 止,一切都在我們的意料之中,但問題很快來了:該函數(shù)的參數(shù)我們使用了值傳遞的方式,按照前面的分析,這需要調(diào)用復(fù)制構(gòu)造函數(shù),于是編譯器又再度搜尋,最 后當(dāng)然又找到了它,于是進(jìn)行調(diào)用,但同樣地,傳參時(shí)又要進(jìn)行復(fù)制,于是再調(diào)用...這個(gè)過程周而復(fù)始,每次都是到了函數(shù)入口處就進(jìn)行遞歸,直到堆棧空間耗 盡,程序崩潰...

??? 當(dāng) 然,這樣的好戲在現(xiàn)實(shí)中不大會(huì)出現(xiàn),因?yàn)榫幾g器會(huì)及時(shí)發(fā)現(xiàn)這一明顯是問題的問題,并報(bào)告錯(cuò)誤,終止編譯;所以接下來我們還得想想其它辦法。我們剛才之所以 沒有獲得想當(dāng)然的成功是由于在傳參時(shí)出現(xiàn)了復(fù)制構(gòu)造函數(shù)的調(diào)用--而這本來就是它需要解決的操作--從而產(chǎn)生無窮遞歸。由是觀之,值傳遞看來是行不通的 了;我想C語言的用戶這時(shí)很快會(huì)反應(yīng)到與值傳遞對(duì)應(yīng)的方式:地址傳遞(傳址),于是聲明變?yōu)檫@樣:

?

??? IntArray(IntArray *p);

?

??? 只作為一般的構(gòu)造函數(shù),它應(yīng)該可以運(yùn)行得很好,但別忘了我們要提供的是復(fù)制構(gòu)造函數(shù),它要求能夠接受一個(gè)同類型對(duì)象,像這樣:

?

??? IntArray a;

??? ... // 對(duì)a操作

??? IntArray b(a);

?

??? 而不是接受指針:

?

??? IntArray a;

??? ... // 對(duì)a操作

??? IntArray b(&a);? // 還要取地址?當(dāng)然,它可以正確運(yùn)行,但...

?

??? 否則,雖然在初始化對(duì)象時(shí)可以像上面一樣人為加一個(gè)取址符,但在函數(shù)參數(shù)表中(或者函數(shù)返回)進(jìn)行值傳遞時(shí),編譯器可不知道在找不著合適定義的情況下牽就選擇你的指針版本。

??? 既然復(fù)制構(gòu)造函數(shù)括號(hào)里放的必須是某個(gè)對(duì)象,但又不能通過值傳遞,也不能通過傳址代替,解決的方案,我想你一定早想到了。沒錯(cuò),就是使用引用:

?

??? IntArray(IntArray& a);

?

??? 由于引用產(chǎn)生的只是一個(gè)別名,而實(shí)質(zhì)上使用的還是“原來的”被引用的對(duì)象,所以,當(dāng)然,嗯,也就不會(huì)存在什么參數(shù)復(fù)制的問題,從而不會(huì)再去調(diào)用復(fù)制構(gòu)造函數(shù)--也就是它自己!

??? 成功之后我們?cè)賮砜纯茨懿荒茉僮鲆恍└倪M(jìn):我們?cè)趶?fù)制對(duì)象的過程中,當(dāng)然,從語義上說,一般不會(huì)改變被復(fù)制的“母體對(duì)象”的值。我們難以想象,諸如:

?

??? Complex b(a);

?

的語句過后,會(huì)使復(fù)數(shù)a 變得面目全非;同時(shí),假如“母體對(duì)象”是const類型,將意味著我們不能用它來初始化其它對(duì)象:因?yàn)閰?shù)IntArray& a不能保證a不會(huì)被修改!將const常量傳給非const引用會(huì)導(dǎo)致編譯出錯(cuò),但這個(gè)限制顯然并非我們所要的;并且,我們有可能不小心的情況在復(fù)制構(gòu)造 函數(shù)中修改了本不希望改動(dòng)的“母體對(duì)象”,而這一切將很難在今后查出。綜上所述,我們通常會(huì)給復(fù)制構(gòu)造函數(shù)的引用參數(shù)前加上const進(jìn)行修飾:

?

??? IntArray(const IntArray& a);

?

??? 這樣一來就更完美了,它避免了上述的問題;而且即使是非const的對(duì)象也可以作為const型的引用(如前文所分析,反過來就不可以)。

?

??? 3.定義的實(shí)現(xiàn)

?

??? 好, 我們已經(jīng)找到了復(fù)制構(gòu)造函數(shù)的一個(gè)合適的參數(shù),對(duì)于聲明的討論也就告一段落了。現(xiàn)在我們還是以IntArray為例,來看看它的實(shí)現(xiàn)應(yīng)當(dāng)是什么樣子。如果 你讀過《構(gòu)造函數(shù)(上)》,那么還記得上一節(jié)開頭我所給出的有關(guān)IntArray的聲明么?如果你的回答是Yes,那么我很佩服你的用心,不過我自己是記 不得了,為了我自己,以及那些同我一樣的朋友和暫時(shí)沒有讀過上節(jié)內(nèi)容的朋友,我把上一節(jié)的IntArray主要部分聲明粘貼如下(其實(shí)很簡單):

?

class IntArray

?

{

?

public:

?

??? ... // others

?

private:

?

??? int *_p;

?

??? int _size;

?

};

?

??? 如 你所推測(cè),IntArray使用整型指針_p存儲(chǔ)在自由存儲(chǔ)區(qū)上以new表達(dá)式分配得來的內(nèi)存空間的首地址,而_size則存放該空間可以容納的整型元素 的個(gè)數(shù),換句話說,它也就代表了數(shù)組的尺寸。以機(jī)器的觀點(diǎn),在IntArray中,_p和_size是標(biāo)識(shí)兩個(gè)IntArray對(duì)象是否相同的特征變量 (我們假設(shè)pulic區(qū)段所聲明的都是成員函數(shù),而沒有成員變量),但從我們對(duì)數(shù)組概念上的理解,_size和_p所指的內(nèi)存中元素是否均對(duì)應(yīng)相同,才是 數(shù)組相同的意義;換言之,_size和_p所指的內(nèi)存的內(nèi)容(而不是_p存儲(chǔ)的地址值)才是構(gòu)成我們“概念理解上”的數(shù)組的信息。因而在進(jìn)行復(fù)制的時(shí)候, 基于尊重“概念抽象”的觀點(diǎn),應(yīng)當(dāng)對(duì)_p所指的內(nèi)存空間進(jìn)行復(fù)制,而不是簡單地復(fù)制_p本身。這就是我們?cè)谧远x的復(fù)制構(gòu)造函數(shù)中所要表明的;下面是一個(gè) 可能的定義,我把分析放在注釋部分:

?

??? IntArray(const IntArray& a)

??? {

??????? _size = a._size;? // 我們顯示定義了復(fù)制構(gòu)造函數(shù)之后,默認(rèn)構(gòu)造函數(shù)就不存在了,

????????????????????????? // 所以必須自己實(shí)現(xiàn)所有必要的成員變量的復(fù)制

?

??????? if (_size > 0)??? // 只有a的尺寸大于0,也就是a有內(nèi)容時(shí),才需要復(fù)制

??????? {

???????????? _p = new int[_size];? // 構(gòu)造函數(shù)總是在創(chuàng)建新對(duì)象時(shí)調(diào)用,所以別忘了分配內(nèi)存

???????????? for (int i = 0; i < _size; ++i)? // 逐個(gè)復(fù)制a中的內(nèi)容

???????????????? _p[i] = a._p[i];

???????? }

???????? else

???????????? _p = 0;???? // 安全起見,我們把零內(nèi)容數(shù)組的指針設(shè)為0

??? }

?

??? 嗯, 再補(bǔ)充一點(diǎn)。如果你的程序非常在意效率,那么你可以用諸如memcopy()的內(nèi)存拷貝函數(shù)把整塊a._p所指的內(nèi)存復(fù)制過來,一般這樣會(huì)比使用循環(huán)快一 些。不過在C++中對(duì)這個(gè)函數(shù)的使用不是不需要經(jīng)過慎重考慮的:和相當(dāng)部分的默認(rèn)復(fù)制構(gòu)造函數(shù)一樣,它只是簡單機(jī)械地拷貝內(nèi)存,所以你如果將此函數(shù)應(yīng)用在 諸如IntArray的對(duì)象上,以希望“有效率地”拷貝多個(gè)IntArray對(duì)象,那么幾乎一定會(huì)出現(xiàn)問題:它不會(huì)知道應(yīng)該拷貝_p所指的內(nèi)容,因而將產(chǎn) 生前面所說的種種問題;甚至我們顯式定義了IntArray的復(fù)制構(gòu)造函數(shù),它也不會(huì)調(diào)用--因?yàn)槟闶褂盟鸵馕吨阋蟮氖恰拔豢截悺保皇恰皬?fù)制構(gòu) 造”。所以,在C++中,只有當(dāng)memcopy應(yīng)用在內(nèi)置基本類型上時(shí),我才敢保證它是安全的。當(dāng)然,如果你把握不準(zhǔn),那么在C++中把這個(gè)C時(shí)代遺留下 來的函數(shù)忘了不失為一個(gè)很好的對(duì)策。

?

??? 4.組合或繼承情況下的復(fù)制構(gòu)造

?

??? 不知你注意到?jīng)]有,我剛剛說memcopy()的機(jī)械拷貝只是和“相當(dāng)部分”的默認(rèn)復(fù)制構(gòu)造函數(shù)一樣,呃,應(yīng)當(dāng)反過來說,只是“相當(dāng)部分”的默認(rèn)復(fù)制構(gòu)造函數(shù)采用“位拷貝”模式。咦,難道還有某種神秘的力量在控制著另一種默認(rèn)復(fù)制構(gòu)造函數(shù)?

??? 先考慮以下例子:假如我現(xiàn)在要設(shè)計(jì)一個(gè)類,不妨叫A,它包含了一個(gè)IntArray對(duì)象作為其中一個(gè)成員,當(dāng)然,這是不奇怪的,像這樣:

?

class A

{

public:

??? ... // others

private:

??? IntArray _array; // that's it

??? ... // others

};

?

??? 然后我的代碼中出現(xiàn)了A的對(duì)象的復(fù)制構(gòu)造,這也是很有可能的,像這樣一個(gè)函數(shù):

?

A dosomething(A a)

{

??? ...????? // 一些操作

??? A b(a);? // 以a復(fù)制構(gòu)造出b

??? ...

??? return b;

}

?

??? 以 上的片斷就隱含了三個(gè)關(guān)于A對(duì)象的復(fù)制構(gòu)造(你看得出來嗎?),但如果我沒有為A定義復(fù)制構(gòu)造函數(shù),如前面所說,編譯器當(dāng)然就會(huì)建立一個(gè)默認(rèn)復(fù)制構(gòu)造函 數(shù),然后在需要的時(shí)候調(diào)用它。問題在于,這個(gè)默認(rèn)復(fù)制構(gòu)造函數(shù)假如還是傻呵呵地采用簡單的位拷貝,那至少可以斷定我們的_array成員將遭遇不測(cè)。當(dāng) 然,你或許會(huì)責(zé)怪我沒有為A添置一個(gè)復(fù)制構(gòu)造函數(shù),但我可以說,而且是很有理由地辯解說,class A的其余成員并不需要提供復(fù)制構(gòu)造函數(shù),至于IntArray類型成員,我只是這個(gè)類的用戶,它封裝好的內(nèi)部機(jī)制不應(yīng)當(dāng)由我負(fù)責(zé),事實(shí)上我也無從負(fù)責(zé):即 使我真的愿意專門為之設(shè)計(jì)一個(gè)復(fù)制構(gòu)造函數(shù),我又該怎么實(shí)現(xiàn)呢?作為用戶,我得到的只是IntArray的接口,也就是說我只有權(quán)利使用它,而無法關(guān)心也 不該它內(nèi)部是怎樣運(yùn)作的,而復(fù)制構(gòu)造函數(shù)通常需要涉及上述內(nèi)容。

??? 作 為一門成功的語言,C++當(dāng)然不能允許存在上述的尷尬。事實(shí)上,假如一個(gè)類存在需要調(diào)用(用戶定義的,可能是間接的)復(fù)制構(gòu)造函數(shù)的成員對(duì)象,而類本身又 沒有提供復(fù)制構(gòu)造函數(shù),則其默認(rèn)構(gòu)造函數(shù)不是簡單地采用位拷貝模式,而是將其對(duì)象“逐一”分別拷貝,對(duì)于需要調(diào)用復(fù)制構(gòu)造函數(shù)的對(duì)象,則進(jìn)行相應(yīng)的調(diào)用, 以保持復(fù)制構(gòu)造的概念上的意義。

??? 對(duì)于繼承的情況也是類似,假如有一個(gè)SuperIntArray類繼承至IntArray:

?

class SuperIntArray: public IntArray

{

public:

??? ... // something

private:

??? ... // something

};

?

??? 即使SuperIntArray本身沒有提供復(fù)制構(gòu)造函數(shù),在其對(duì)象需要復(fù)制構(gòu)造時(shí),對(duì)基類IntArray部分的構(gòu)造,同樣也會(huì)調(diào)用IntArray(const IntArray&)來實(shí)現(xiàn)。

??? 嗯,如果前面所提到的組成與繼承的情況中,新的類型顯示提供了復(fù)制構(gòu)造函數(shù),則相應(yīng)成員,或者基類的復(fù)制構(gòu)造函數(shù)也同樣會(huì)被調(diào)用。實(shí)際上前一節(jié)我們提到構(gòu)造函數(shù)時(shí)已經(jīng)就這個(gè)問題討論過了,所以這一部分基本上沒有太多的新意:別忘了,復(fù)制構(gòu)造函數(shù)只不過是構(gòu)造函數(shù)的一種。

?