天天看點

何時需要自定義複制構造函數?

  本文涉及對象的指派和複制(也稱為克隆)。必要時,先看譚浩強教材p291-295的相關内容或ppt,重溫一下有關概念。

  一、一般情況

  先看一個例子:

注意對相加運算重載函數的定義:

  從變量的作用域角度講,complex c是函數的局部變量,意味着當函數執行完後,c占用的記憶體空間将被釋放。那麼,在main()函數中調用c3=c1+c2時,c3能夠得到正确的結果嗎?答案是肯定的,在operate+(c1,c2)的最後,執行return c 的時候,c 被傳回,通過c3=c1+c2中的指派(=),c 的值被(複制)指派給了c3,在完成使命之後,c 潇灑謝幕。

  對象的指派(=)運算符的重載是預設的,不需要專門定義對“=”的重載去完成自定義類中對象的指派。但是,這裡有一個前提,類中不能包括動态配置設定的資料,否則“可能出現嚴重的後果”(譚浩強教材p293頁)。那這個嚴重的後果是什麼呢?稍後講。

  與對象的指派相類似的還有對象的複制。其實,在函數傳回值為對象的時候,系統會将其中傳回的對象複制出一個新的臨時對象,并傳遞給該函數的調用處。在複制中,需要用到複制構造函數,但這個複制構造函數一般也不需要使用者定義,系統可以自動完成。這個“一般”暗示着什麼?傳回的對象中不能包括動态配置設定的資料。

  那我們勇敢一些,去以身試法,看看如果對象中包含了動态配置設定的資料後究竟會發生什麼事情。領教一下後果不是目的,目的在于找到解決的辦法。因為這也是實際應用中必須面臨的問題。

  二、以身試法——傳回包含動态配置設定資料的臨時對象

  也從一個例子開始。下面的例子建立一個二維數組類douary,完成矩陣的輸入、輸出和相加操作。與例程1 的差別是,資料成員中有指針,并其指針指向的空間在構造函數中動态配置設定,在析構函數中釋放。

  在operate+函數中,與例程1的operate+ 一樣,聲明了一個臨時的局部變量,經過一些運算後,函數傳回這個局部變量。

  那結果又如何呢?看來是領教“嚴重後果”的時候了。

  程式運作的結果是這樣的:

  我們看到,相加結果是錯誤的!在某些時候,類似的程式是彈出一個視窗,報告記憶體溢出。

  原因何在?在例程2中,第62 行return d; 後仍然也執行預設的複制構造函數将 d 對象複制給main()函數中的一個臨時變量再指派給了對象d3(第73行),複制完後,d 的空間被釋放。d3的array(指針)指向的空間,此時顯然已經不能由d3繼續使用,而是可以被系統配置設定了。d3的array指向一個無法控制的空間,後果真的很嚴重。

  三、解決辦法

  究其原因,是因為預設的構造函數過于簡單,幹不了複制“帶有需要動态配置設定空間的資料成員”類的“瓷器活”。實際上,當類中無動态配置設定空間的資料成員時,複制工作也就是對應的成員逐一複制,而有了動态配置設定空間的資料成員,那是各有各的樣,沒法統一。于是在這個時候,需要我們做的是,自己定義複制構造函數,關鍵是在複制的時時候,動态配置設定相應的空間,将完整的對象複制下來。

  例程2改進之後為:(注意新增加的複制構造函數douary(const douary &d);的聲明(第8行)和定義(第28-36行)即可,其他位置同例程2完全一樣)

  四、總結

  當類中的資料成員需要動态配置設定存儲空間時,不可以依賴預設的複制構造函數。在需要時(包括這種對象要指派、這種對象作為函數參數要傳遞、函數傳回值為這種對象等情況),要考慮到自定義複制構造函數。

  另外,複制構造函數一經定義,指派運算也按新定義的複制構造函數執行。

<本文完>