[轉(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ù)的一種。
?