複製控制
一、複製控制
類能控制複製,賦值,撤銷該類的對象時的動作,分別通過下面的成員函數:
1. 複製構造函數:具有單個形參,該形參是對該類類型的引用(通常用 const修飾);
2. 賦值操作符
3. 析構函數:不管有沒顯示定義,編譯器都自動執行類中非static數據成員的析構函數
這三個函數就成爲複製控制。
二.爲什麼要研究複製控制
如果沒有顯示地定義複製構造函數和賦值操作符,編譯器會爲我們定義。
但是編譯器合成的複製控制函數只做必需的工作,某些類如果依賴於默認的定義會導致錯誤,例如類具有指針成員。
難點: 識別何時需要覆蓋默認版本,定義自己的複製構造函數
所以有時候要自定義複製構造函數,定義複製構造函數跟構造函數是一樣的,難點是能認識到需要定義複製構造函數。
(具體請看第五點:”什麼時候要自己的複製構造函數”)
複製控制函數
三.複製構造函數會幾時用到?
首先:區分構造函數和複製構造函數
1.根據另一個同類型的對象顯式或者隱式地初始化一個對象
區別下面:
(1)下面的構造函數和複製構造函數僅在低級別優化上存在差異
String str3(10,"a"); // 調用構造函數
String str4 ; // 調用構造函數
String str1= "hello"; // 調用複製構造函數
String str2 = string(); // 調用複製構造函數
(2)對於不支持複製的類型,或者使用非explicit構造函數的時候,它們有本質區別:
Ifstream file1("filename"); //OK,調用構造函數
Ifstream file2 = "filename" ; //ERROR, 不能複製IO類型的對象
//(複製構造函數是private 的)(原因見第七項)
Sales_item item = string("hello"); //取決於構造函數是不是explicit,如果構造函數 是顯式的,則初始化失敗,如果構造函數是隱式的,則初始化成功。
2.一個對象作爲實參傳給一個函數,或者作爲函數的返回值
例如: string fun(const stirng& A, const string B);
返回值和 第二個參數B隱式地調用複製構造函數;第一個參數A,是const引用,不能複製。
3.初始化順序容器中的元素
當用表示容量的單個形參來初始化容器的時候,用到了默認構造函數和複製構造函數。
Vector<string> svec(5);
先用string默認構造函數創建一個臨時值來初始化svec,然後是使用複製構造函數將臨時值複製到svec的每一個元素。
4.根據“元素初始化列表"初始化數組元素
(1)如果沒有爲類類型數組提供元素初始化式,是調用默認構造函數初始化每個元素,
Sale_item ClassArray[];
(2)如果用花括號擴展的數組初始化列表來提供顯式元素初始化,則是用複製構造函數.
根據指定值創建適當類型的元素,然後用複製構造函數將該值複製到相應的元素。
Sale_item ClassArray2[] = { string("ab"), //直接指定一個值,調用單實參構造函數
string("cd"),
Sale_item(), // 使用完整的構造函數語法(0或者多個實參)
};
備註:可以直接指定一個值,調用元素類型的單實參構造函數,如前兩個元素的初始化;
如果不指定實參或者指定多個實參,就需要使用完整的構造函數語法,如最後一個元素的初始化
四.編譯器合成的複製構造函數
前面提到,如果我們沒有定義,編譯器會默認合成一個複製構造函數,該函數會對該對象的每一個非static成員依次複製到正創建的對象。
其中,不同類型的複製如下:
1.直接複製內置類型成員的值(不是指針)
2.類型成員使用該類的複製構造函數
3.數組複製數組的每一個元素
等價於: 一個構造函數,每個數據成員在構造函數初始化列表中進行初始化;
例如 有三個數據成員的類的合成構造函數:
Sales_item::Sales_item(const Sales_item &orig)
A(orig.A),
B(orig.B),
C(orig.C)
{//Empty}
五.什麼時候要自己定義複製構造函數(重點)
有些類必須對複製對象時發生的事情進行控制,例如
1、類中有數據成員是指針或者有成員在構造函數中分配其他資源
2、在創建對象時必須做一些特定工作
以上的情況都必須定義複製構造函數。
六、怎麼定義複製構造函數(同構造函數一樣)
同類同名,沒有返回值,可以使用構造函數初始化列表初始化新創建對象的成員,可以在函數體中任何其他必要的工作。
七、如何禁止複製
例如:iostream類就不允許複製
1.如果不自定義複製構造函數,編譯器也會自動合成一個,無法禁止複製
2.爲了防止複製,類必須顯式地聲明其複製構造函數爲 private(其友元和成員依然可以複製)
3.如果要連友元和成員的複製也禁止,就可以聲明一個private的複製構造函數但不對它進行定義。(如果複製類對象會提示編譯錯誤,如果成員和友元嘗試複製就會導致鏈接錯誤)
八、賦值操作符
1.如果沒有定義自己的賦值操作符,編譯器也會自動合成一個(類似於合成的複製構造函數,進行各個成員的賦值)
2.如果類自定義自己的複製操作符,那麼一般類也需要自定義賦值操作符(具體的請看後面的例子)
九、析構函數
1、 析構函數通常用於釋放在構造函數或者在對象生命期內獲取的資源。析構函數可以執行任意操作,一般是在類對象使用完畢之後要執行的動作。
三法則: 如果類需要析構函數,則它也需要複製操作符和複製構造函數。
2、合成的析構函數按創建的逆序撤銷每個非static 成員。(並不刪除指針成員所指向的對象),無論有沒創建自定義函數,編譯器都會合成析構函數
3、注意:即便自定義了自己的析構函數,編譯器在運行自定義析構函數之後,還會運行合成析構函數。