《C++代码设计与重用》——2.5 浅拷贝和深拷贝
本節(jié)書(shū)摘來(lái)自異步社區(qū)出版社《Imperfect C++中文版》一書(shū)中的第2章,第2.5節(jié),作者: 【美】Martin D.Carroll , Margaret A.Ellis,更多章節(jié)內(nèi)容可以訪問(wèn)云棲社區(qū)“異步社區(qū)”公眾號(hào)查看。
2.5 淺拷貝和深拷貝
C++代碼設(shè)計(jì)與重用
2.5 淺拷貝和深拷貝
有兩個(gè)操作,盡管它們具有某些不合乎需要的特性,但因?yàn)樗鼈兊氖褂梅秶軓V,進(jìn)而博得一定的注意,所以這兩個(gè)操作在這里有必要特別提及一下,這兩個(gè)操作就是淺拷貝操作和深拷貝操作。x對(duì)象的淺拷貝是指:另一個(gè)和x相同類(lèi)型的,并且它的數(shù)據(jù)成員和x相對(duì)應(yīng)的數(shù)據(jù)成員具有相同值的對(duì)象。x對(duì)象的深拷貝是指:另一個(gè)和x類(lèi)型相同的對(duì)象,它具有x直接或間接指向的對(duì)象的一份拷貝,并且在拷貝里,所有共享和循環(huán)的聯(lián)系依舊保留。考慮下面3個(gè)類(lèi):
在圖2.1中,x2是X類(lèi)型對(duì)象x1的淺拷貝,x3是x1的深拷貝。
但是,對(duì)一個(gè)設(shè)計(jì)得很好,并且正確實(shí)現(xiàn)的程序庫(kù)(幾乎沒(méi)有異常產(chǎn)生),它的用戶應(yīng)該可以請(qǐng)求創(chuàng)建某個(gè)對(duì)象的拷貝—通過(guò)拷貝構(gòu)造函數(shù)—并且由程序庫(kù)適當(dāng)實(shí)現(xiàn)某種對(duì)象類(lèi)型的拷貝。除了一些特殊的類(lèi)之外,用戶是不需要了解拷貝函數(shù)的實(shí)現(xiàn)機(jī)制的,也不應(yīng)該指定用某種特殊的方式來(lái)拷貝一個(gè)對(duì)象。
讓我們還是回到討論的話題,對(duì)一個(gè)給定的類(lèi),我們很少用淺拷貝操作或深拷貝操作來(lái)實(shí)現(xiàn)它的拷貝構(gòu)造函數(shù)。假設(shè)我們用下面代碼來(lái)實(shí)現(xiàn)2.1節(jié)中的Rational類(lèi):
class Rational { private:Rational_rep* rep;//...};class Rational_rep {private:int num;int denom;//...};在這里,我們只是簡(jiǎn)單地把成員變量num和denom移到一個(gè)單獨(dú)的類(lèi)里面。(我們將在8.2.4節(jié)看到,這種實(shí)現(xiàn)方法為類(lèi)Rational的用戶提供了鏈接兼容性。)下面是類(lèi)Rational和類(lèi)Rational_rep用深拷貝來(lái)實(shí)現(xiàn)的拷貝構(gòu)造函數(shù):
class Rational {public:Rational(const Rational& r) {Rep = new Rational_rep(*r.rep);}//...};class Rational_rep {public:Rational_rep(Rational_rep& rep) :num(rep.num),denom(rep.denom) {}//...);當(dāng)用淺拷貝或者深拷貝來(lái)正確實(shí)現(xiàn)拷貝構(gòu)造函數(shù)的時(shí)候,我們應(yīng)該理所當(dāng)然地類(lèi)似(淺拷貝還有很大區(qū)別)上面那樣來(lái)實(shí)現(xiàn)這個(gè)構(gòu)造函數(shù)。但是,上面代碼之所以可以實(shí)現(xiàn),也僅僅是某種巧合;我們并不向用戶建議這種巧合。如果類(lèi)的實(shí)現(xiàn)改變了,那么拷貝構(gòu)造函數(shù)就可能不再實(shí)現(xiàn)淺拷貝或者深拷貝了。實(shí)際上,用戶也不應(yīng)該委托拷貝構(gòu)造函數(shù)實(shí)現(xiàn)這兩種拷貝。此外,這種(巧合)現(xiàn)象—指通過(guò)淺拷貝或者深拷貝來(lái)實(shí)現(xiàn)類(lèi)的拷貝構(gòu)造函數(shù)—發(fā)生的概率要比程序員所認(rèn)為的少很多。因?yàn)閷?duì)大部分類(lèi)來(lái)說(shuō),淺拷貝和深拷貝都不能用來(lái)實(shí)現(xiàn)類(lèi)的拷貝構(gòu)造函數(shù);并且對(duì)一些特殊的類(lèi),淺拷貝和深拷貝往往帶有不適合需要的屬性:它們不能保持程序的不變性。例如,為了盡可能地共享類(lèi)Rational_rep,我們可以這樣來(lái)改變類(lèi)Rational的實(shí)現(xiàn):
Struct Rational_rep {int refcnt; //引用計(jì)數(shù)。//...};class Rational {public:Rational(const Rational& r) {red = r.rep;++rep->refcnt;}//...};一般當(dāng)我們要共享某個(gè)對(duì)象的時(shí)候,我們必須增加引用計(jì)數(shù),用它來(lái)決定在什么時(shí)候可以刪除一個(gè)共享對(duì)象。對(duì)于任何使用類(lèi)Rational這個(gè)版本的程序,它的不變性是指類(lèi)Rational_rep里面的引用計(jì)數(shù)等于指向這個(gè)共享Rational_rep的類(lèi)Rational的數(shù)目。讀者容易看出,創(chuàng)建類(lèi)Rational的淺拷貝將會(huì)違背這個(gè)不變性。
類(lèi)Rational的問(wèn)題也并不局限于淺拷貝。考慮下面的轉(zhuǎn)型,它在Rational不為零的情況下返回真值。
class Rational {public:operator bool() const {return rep->num != 0;}//...};假設(shè)用戶經(jīng)常調(diào)用這個(gè)函數(shù)。為了優(yōu)化這個(gè)函數(shù)的實(shí)現(xiàn),讓我們改變類(lèi)Rational的實(shí)現(xiàn),來(lái)使所有值為零的Rational對(duì)象都指向同一個(gè)Rational_rep對(duì)象:
class Rational {public:operator bool() const {return rep == rep_of_zero;}//...private:static Rational_rep* rep_of_zero;//...};這個(gè)bool轉(zhuǎn)型函數(shù)避免了一個(gè)間接的調(diào)用(很顯然獲得了一些效率的優(yōu)化,但這僅僅是一個(gè)例子)。讀者容易核實(shí),創(chuàng)建一個(gè)值為零的Rational對(duì)象的深拷貝,將會(huì)破壞不變性,這里的不變性是指,所有值為零的Rational對(duì)象都指向同一個(gè)Rational_rep對(duì)象1。
如果淺拷貝或者深拷貝操作有可能破壞某個(gè)不變性定義的話,那么類(lèi)的實(shí)現(xiàn)者當(dāng)然可以通過(guò)插入代碼以恢復(fù)這個(gè)不變性,從而避免破壞。然而,保存不變性這個(gè)做法并沒(méi)有改變淺拷貝或深拷貝破壞不變性的這個(gè)事實(shí)。況且,在更加復(fù)雜的類(lèi)里面,淺拷貝或者深拷貝操作也照樣破壞不變性,并且這種破壞性是很難甚至不可能得到修復(fù)的(見(jiàn)練習(xí)2.5c)。
假設(shè)類(lèi)X的淺拷貝和深拷貝操作不會(huì)破壞任何不變性,那么類(lèi)X是否應(yīng)該提供這些操作呢?見(jiàn)下面例子:
class X {public:X* shallow_copy(); //應(yīng)該提供這個(gè)函數(shù)嗎?X* deep_copy(); //應(yīng)該提供這個(gè)函數(shù)嗎?//...};當(dāng)然不應(yīng)該,除非X是那些非常特殊的類(lèi),并且允許用戶指定它的拷貝實(shí)現(xiàn)方式。另一方面,如果我們想改變類(lèi)X的實(shí)現(xiàn),又不得破壞不變性,并且提供淺拷貝和深拷貝操作,那么這種改變也是不可能實(shí)現(xiàn)的。因此,我們可以這樣認(rèn)為:類(lèi)一般不應(yīng)該提供淺拷貝和深拷貝操作(見(jiàn)練習(xí)2.5d)。
1譯注:由深拷貝的定義可知,如果執(zhí)行深拷貝操作,那么也將拷貝出一個(gè)新的Rational rep對(duì)象,這與要求只有同一個(gè)Rational rep對(duì)象矛盾。
本文僅用于學(xué)習(xí)和交流目的,不代表異步社區(qū)觀點(diǎn)。非商業(yè)轉(zhuǎn)載請(qǐng)注明作譯者、出處,并保留本文的原始鏈接。
總結(jié)
以上是生活随笔為你收集整理的《C++代码设计与重用》——2.5 浅拷贝和深拷贝的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《C++面向对象高效编程(第2版)》——
- 下一篇: C++智能指针剖析(上)std::aut