天天看點

C++筆記:複制控制

複制控制

定義一個新類型的時候,需要顯式或隐式地指定

複制

指派

撤銷

該類型的對象時會發生什麼,這是通過定義特殊成員:

複制構造函數

指派操作符

和 

析構函數

來達到的。如果沒有顯式定義複制構造函數或指派操作符,編譯器(通常)會為我們定義。

複制構造函數、指派操作符和析構函數總稱為複制控制。

編譯器自動實作這些操作,但類也可以定義自己的版本。

編譯器合成的複制控制函數是非常精練的,它們隻做必需的工作。但對某些類而言,依賴于預設定義會導緻災難。實作複制控制操作最困難的部分,往往在于識别何時需要覆寫預設版本。有一種特别常見的情況需要類

定義自己的複制控制成員的:類具有指針成員。

複制構造函數

複制構造函數是一種

特殊構造函數

,具有

單個形參

,該形參(常用const修飾)是對該類類型的

引用

。與預設構造函數一樣 ,複制構造函數可由編譯器隐式調用。

使用複制構造函數

  • 根據另一個同類型的對象顯式或隐式初始化一個對象。
  • 複制一個對象将它作為實參傳給一個函數。
  • 從函數傳回時複制一個對象。
  • 初始化順序容器中的元素。[Code3]
  • 根據元素初始化式清單初始化數組元素。[Code4]

對象的定義形式

  • C++支援兩種初始化形式:

    直接初始化

    複制初始化

    。複制初始化使用“=”符号,而直接初始化将初始化式放在圓括号中。
  • 當用于類類型對象時,初始化的複制形式和直接形式有所不同。[Code1]
    • 直接初始化直接調用與實參比對的構造函數
    • 複制初始化首先使用指定

      構造函數建立一個臨時對象

      ,然後用

      複制構造函數

      将那個臨時對象複制到正在建立的對象。
  • 支援初始化的複制形式主要是為了與C的用法相容。

    當情況許可時,可以允許編譯器跳過複制構造函數直接建立對象,但編譯器沒有義務這樣做。
  • 通常直接初始化和複制初始化僅在低級别優化上存在差異。
  • 對于不支援複制的類型,或者使用explicit構造函數,不能進行複制初始化。[Code2]

合成的複制構造函數

  • 沒有定義複制構造函數,編譯器就會為我們合成一個。與合成的預設構造函數不同,即使我們

    定義了其他構造函數,也會合成複制構造函數。

  • 合成複制構造函數的行為是,執行逐個成員初始化,将新對象初始化為原對象的副本。
    • 編譯器将現在對象的每個

      非static成員

      ,依次複制到正建立的對象。隻有一個例外,每個成員的類型決定了複制該成員的含義。
    • 合成複制構造函數

      直接複制内置類型成員的值

      ,類類型成員使用

      該類的複制構造函數進行複制

    • 數組成員的複制是個例外。雖然一般不能複制數組,但如果一個類具有數組成員,則

      合成複制構造函數将複制數組的每一個元素。

定義自己的複制構造函數

  • 複制構造函數就是接受單個類類型引用形參(通常用const修飾)的構造函數

    Foo(const Foo&)

  • 因為用于向函數傳遞對象和從函數傳回對象,該構造函數一般不應設定為explicit
  • 合成複制構造函數隻完成必要的工作。

    隻包含類類型成員或内置類型(但不是指針類型)成員的類

    ,無須顯式地定義複制構造函數,也可以複制。
  • 有些類必須定義複制構造函數對複制對象時發生的事情加以控制。
    • 類有一個資料成員是

      指針

      ,或者有成員表示在構造函數中

      配置設定的其他資源

    • 類在建立新對象時必須做一些特定工作。
  • 如果定義了複制構造函數,也必須定義預設構造函數。

禁止複制

  • 為了防止複制,類必須顯式聲明其複制構造函數為private。如果不定義複制構造函數,編譯器将合成一個。
  • 然而,類的友元和成員仍可以進行複制。如果想要連友元和成員中的複制也禁止,就可以

    聲明一個(private)複制構造函數但不對其定義。

    • 聲明而不定義成員函數是合法的,但是,使用未定義成員的任何嘗試将導緻連結失敗。通過聲明(但不定義)private複制構造函數,可以禁止任何複制類類型對象的嘗試:使用者代碼中複制嘗試将在編譯時标記為錯誤,而成員函數和友元中的複制嘗試将在連結時導緻錯誤。

指派操作府

與構造函數一樣,指派操作符可以通過指定不同類型的右操作數而重載。右操作數為類類型的版本比較特殊:如果我們沒有編寫這種版本,編譯器将為我們合成一個。

Sales_item trans, accum;trans = accum;

合成的指派操作符

  • 合成指派操作符與合成複制構造函數的操作類似。它會執行逐個成員指派: 右操作數對象的每個成員指派給左操作數對象的對應成員。除數組之外,每個成員用所屬類型的正常方式進行指派。對于數組,給每個數組元素指派。[Code5]
  • 一般而言,如果類需要複制構造函數,它也會需要指派操作符。

析構函數

是構造函數的互補:當對象超出作用域或動态配置設定的對象被删除時,将自動應用析構函數。析構函數可用于釋放對象時構造或在對象的生命期中所擷取的資源。

不管類是否定義了自己的析構函數,編譯器都自動執行類中非static資料成員的析構函數。

  • 析構函數的調用
    • 撤銷類對象時會自動調用析構函數
    • 動态配置設定的對象隻有在指向該對象的指針被删除時才撤銷
    • 當對象的引用或指針超出作用域時,不會運作析構函數。隻有删除指向動态配置設定對象的指針或實際對象(而不是對象的引用)超出作用域時,才會運作析構函數。
    • 撤銷一個容器(不管是标準庫容器還是内置數組)時,也會運作容器中的類類型元素的析構函數,容器中的元素總是按

      逆序撤銷

  •  什麼時候需要編寫顯式的析構函數
    • 釋放資源或者設計者希望在該類對象的使用完畢之後執行的操作
    • 如果類需要析構函數,則它也需要指派操作符和複制構造函數,這是一個有用的經驗法則。
  • 合成析構函數
    • 與複制構造函數或指派操作符不同,編譯器總是會為我們合成一個析構函數。合成析構函數按對象建立時的逆序撤銷每個非static成員,是以,它按成員

      在類中聲明次序的逆序撤銷成員。

    • 合成析構函數并不删除指針成員所指向的對象。
  • 編寫析構函數
    • 析構函數是個成員函數,它的名字是在類名字之前加上一個代字号(~),它沒有傳回值,沒有形參。因為不能指定任何形參,是以

      不能重載析構函數。

    • 雖然可以為一個類定義多個構造函數,但

      隻能提供一個析構函數

      ,應用于類的所有對象
    • 析構函數與複制構造函數或指派操作符之間的一個重要差別是,

      即使我們編寫了自己的析構函數,合成析構函數仍然運作。

      [Code6]

Code

Code1:類類型的直接初始化和複制初始化

string null_book = "9-999-99999-9"; // copy-initialization  
string dots(10, '.'); // direct-initialization  

string empty_copy = string(); // copy-initialization  
string empty_direct; // direct-initialization
           

Code2:不支援複制初始化到例子

ifstream file1("filename"); // ok: direct initialization 
ifstream file2 = "filename"; // error: copy constructor is private 
// This initialization is okay only if the Sales_item(const string&) constructor is not explicit 
Sales_item item = string("9-999-99999-9");
           

Code3:初始化容器元素

/*複制構造函數可用于初始化順序容器中的元素。例如,可以用表示容量的單 個形參來初始化容器。容器的這種構造方式使用預設構造函數和複制構造函數*/
 // default string constructor and five string copy constructors invoked
 vector<string> svec(5);
/*編譯器首先使用 string 預設構造函數建立一個臨時值來初始化 svec,然 後使用複制構造函數将臨時值複制到 svec 的每個元素。*/
           

Code4:複制構造函數與數組元素

/*如果沒有為類類型數組提供元素初始化式,則将用預設構造函數初始化每個 元素。然而,如果使用正常的花括号包覆的數組初始化清單(第 4.1.1 節)來 提供顯式元素初始化式,則使用複制初始化來初始化每個元素。根據指定值建立 适當類型的元素,然後用複制構造函數将該值複制到相應元素:*/
Sales_item primer_eds[] = { string("0-201-16487-6"),
                             string("0-201-54848-8"),
                             string("0-201-82470-1"),
                             Sales_item()
                           };
/*如前三個元素的初始化式中所示可以直接指定一個值,用于調用元素類型的 單實參構造函數。如果希望不指定實參或指定多個實參,就需要使用完整的構造 函數文法,正如最後一個元素的初始化那樣。*/
           

Code5:合成的指派操作符

// equivalent to the synthesized assignment operator
Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
isbn = rhs.isbn; // calls string::operator= 
units_sold=rhs.units_sold; //uses built-in int assignment 
revenue = rhs.revenue; //uses built-in double assignment
return *this;
}
/*合成指派操作符根據成員類型使用适合的内置或類定義的指派操作符,依次給每個成員指派,該操作符傳回 *this,它是對左操作數對象的引用。*/
           

Code6:析構函數

class Sales_item {
     public:
     // empty; no work to do other than destroying the members,which happens automatically
     ~Sales_item() { }
     // other members as before
};
/*撤銷 Sales_item 類型的對象時,将運作這個什麼也不做的析構函數,它執 行完畢後,将運作合成析構函數以撤銷類的成員。合成析構函數調用 string 析 構函數來撤銷 string 成員,string 析構函數釋放了儲存 isbn 的記憶體。 units_sold 和 revenue 成員是内置類型,是以合成析構函數撤銷它們不需要做什麼。*/
           

From:

http://blog.csdn.net/liufei_learning/article/details/21312701

繼續閱讀