引言:
當定義一個新類型時,需要顯式或隐式地指定複制、指派和撤銷該類型的對象時會發生什麼– 複制構造函數、指派操作符和析構函數的作用!
複制構造函數:具有單個形參,該形參(常用const修飾)是對該類類型的引用。當定義一個新對象并用一個同類型的對象對它進行初始化時,将顯式的使用複制構造函數;當将該類型的對象傳遞給函數或者從函數傳回該類型的對象時,将隐式使用複制構造函數。
析構函數:作為構造函數的互補,當對象超出作用域或動态配置設定的對象被删除時,将自動應用析構函數。
指派操作符:與構造函數一樣,複制操作符可以通過指定不同類型的右操作數而重載。右操作數為類類型的版本比較特殊:如果我們沒有編寫這種版本,則編譯器将為我們合成一個。
【小心地雷】
通常,編譯器為我們合成的複制構造函數函數是非常精煉的---它們隻做必須的工作,但對于類而言,依賴于預設定義有時會導緻災難!
一、複制構造函數
隻有單個形參,而且該形參是對本類類型對象的引用(常用const修飾),這樣的構造函數稱為複制構造函數。與預設構造函數一樣,複制構造函數可由編譯器隐式調用。複制構造函數可用于:
1)根據另一個同類型的對象顯式或隐式初始化一個對象。
2)複制一個對象,将它作為實參傳給一個函數。
3)從函數傳回時複制一個對象。
4)初始化順序容器中的元素。
5)根據元素初始化式清單初始化數組元素。
1、對象的定義形式
對于類類型,初始化的複制形式和直接形式有所不同:直接初始化直接調用與實參比對的構造函數,複制初始化式總是調用複制構造函數。[複制初始化首先使用指定構造函數建立一個臨時對象,然後複制構造函數将那個臨時對象複制到正在建立的對象!]
對于類類型對象,隻有指定單個實參或顯式建立一個臨時對象用于複制時,才使用複制初始化!
支援初始化的複制形式主要是為了與c的用法相容,當情況許可時,可以允許編譯器跳過複制構造函數函數直接建立對象,但是編譯器沒有義務這樣做!
通常直接初始化和複制初始化僅在低級别上存在差異。然而,對于不支援複制的類型,或者使用非explicit構造函數的時候,它們有本質差別:
2、形參與傳回值
當形參為非引用類型的時候,将複制實參的值,類似的,以非引用類型作傳回值時,将傳回return語句中值的副本。
3、初始化容器元素
【推薦】
作為一般規則,除非你想使用容器元素的預設初始值,更有效的辦法是,配置設定一個空容器并将已知元素的值加入容器。
4、構造函數與數組元素
如果沒有為類類型數組提供元素初始化式,則将用預設構造函數初始化每個元素。然而,如果使用正常的花括号包覆的數組初始化清單來提供顯式元素初始化式,則使用複制初始化來初始化每個元素。根據指定值建立适當類型的元素,然後用複制構造函數将該值複制到相應元素:
合成的複制構造函數
合成複制構造函數的行為是:執行逐個(非static)成員初始化,将新對象初始化為原對象的副本!
合成複制構造函數直接複制内置類型成員的值,類類型成員使用該類的複制構造函數進行複制。數組成員的複制是個例外。雖然一般不能複制數組,但如果一個類具有數組成員,則合成複制構造函數将複制數組。複制數組時合成複制構造函數将複制數組的每一個元素。
逐個成員初始化最簡單的概念模型是,将合成複制構造函數看作這樣一個構造函數:其中每個資料成員在構造函數初始化清單中進行初始化。
定義自己的複制構造函數
複制構造函數的形參通常是一個const引用;因為由于向函數傳遞對象和從函數傳回對象,該構造函數一般不應設定為explicit!
對許多類而言,合成複制構造函數隻完成必要的工作。隻包含類類型成員或内置類型(但不是指針類型)成員的類,無須顯式地定義複制構造函數,也可以複制。
然而,有些類必須對複制對象時發生的事情加以控制。這樣的類經常有一個資料成員是指針或者有成員表示在構造函數中配置設定的其他資源,而另一些類在建立新對象時必須做一些特定工作。這兩種情況下,都必須定義複制複制構造函數!
通常,定義複制構造函數最困難的部分在于認識到需要複制構造函數o(∩_∩)o~。隻要能認識到需要複制構造函數,定義構造函數一般非常簡單。複制構造函數的定義與其他構造函數一樣:它與類同名,沒有傳回值,可以(而且應該)使用構造函數初始化清單初始化新建立對象的成員,可以在函數體中做任何其他必要工作。
禁止複制
有些類需要完全禁止複制。例如,iostream類就不允許複制。如果想要禁止複制,似乎可以省略複制構造函數,然而,如果不定義複制構造函數,編譯器将合成一個。
通過聲明但不定義private複制構造函數可以禁止任何複制類類型對象的嘗試:用于代碼中的複制嘗試将在編譯時标記為錯誤,而成員函數和友元中的複制嘗試将在連結時導緻錯誤!
大多數類應定義複制構造函數和預設構造函數
不定義複制構造函數和/或預設構造函數,會嚴重局限類的使用:不允許複制的類對象隻能作為引用傳遞給函數或從函數傳回,它們也不能用作容器的元素。
一般來說,最好顯式或隐式定義預設構造函數和複制構造函數。隻有不存在其他構造函數時才合成預設構造函數。如果定義了複制構造函數,也必須定義預設構造函數。
二、指派操作符
與複制構造函數一樣,如果類沒有定義自己的指派操作符,則編譯器會合成一個!
1、介紹重載指派
重載操作符是一些函數,其名字為operator後跟着所定義的操作符的符号。是以,通過定義名為operator=的函數,我們可以對指派進行定義。像任何其他函數一樣,操作符函數有一個傳回值和一個形參表。形參表必須具有與該操作符數目相同的形參(如果操作符是一個類成員,則包括隐式this形參)。指派是二進制運算,是以該操作符函數有兩個形參:第一個形參對應着左操作數,第二個形參對應右操作數。
大多數操作符可以定義為成員函數或非成員函數。當操作符為成員函數時,它的第一個操作數隐式綁定到this指針。有些操作符(包括指派操作符)必須是定義自己的類的成員。因為指派必須是類的成員,是以this綁定到指向左操作數的指針。是以,指派操作符接受單個形參,且該形參是同一類類型的對象。右操作數一般作為const引用傳遞。
指派操作符也傳回對同一類類型的引用。
2、合成指派操作符
合成指派操作符會執行逐個成員指派:右操作數對象的每個成員指派給左操作數對象的對應成員。除數組之外,每個成員用所屬類型的正常方式進行指派。對于數組,給每個數組元素指派。如:
3、複制和指派常一起使用
實際上,應該将複制構造函數和指派操作符看做一個單元,如果需要其中一個,我們幾乎也可以肯定需要另一個!