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