複製控制(學習)

複製控制(學習<C++Primer>)

複製控制

一、複製控制

類能控制複製,賦值,撤銷該類的對象時的動作,分別通過下面的成員函數:

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、注意:即便自定義了自己的析構函數,編譯器在運行自定義析構函數之後,還會運行合成析構函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章