複製構造函數

0. 複製構造函數

只有單個形參,並且該形參是對本類類型對象的引用(經常使用 const 修飾),這樣的構造函數稱爲複製構造函數ios

與默認構造函數同樣,複製構造函數可由編譯器隱式調用。
複製構造函數可用於:
  根據另外一個同類型的對象顯式或隱式初始化一個對象。
  複製一個對象,將它做爲實參傳給一個函數。
  從函數返回時複製一個對象。
  初始化順序容器中的元素。
  根據元素初始化式列表初始化數組元素。數組

對象的定義形式函數

回憶一下,C++ 支持兩種初始化形式:
直接初始化和複製初始化。複製初始化使用 = 符號,而直接初始化將初始化式放在圓括號中。spa

當用於類類型對象時,初始化的複製形式和直接形式有所不一樣:
直接初始化直接調用與實參匹配的構造函數,複製初始化老是調用複製構造函數。指針

複製初始化首先使用指定構造函數建立一個臨時對象,而後用複製構造函數將那個臨時對象複製到正在建立的對象:code

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

對於類類型對象,只有指定單個實參或顯式建立一個臨時對象用於複製時,才使用複製初始化。
建立 dots 時,調用參數爲一個數量和一個字符的 string 構造函數並直接初始化 dots 的成員。
建立 null_book 時,編譯器首先調用接受一個 C 風格字符串形參的 string 構造函數,建立一個臨時對象,
而後,編譯器使用 string 複製構造函數將 null_book 初始化爲那個臨時對象的副本。
empty_copy 和 empty_direct 的初始化都調用默認構造函數。
對前者初始化時,默認構造函數函數建立一個臨時對象,而後複製構造函數用該對象初始化 empty_copy。
對後者初始化時,直接運行 empty_direct 的默認構造函數。對象

支持初始化的複製形式主要是爲了與 C 的用法兼容。
當狀況許可時,能夠容許編譯器跳過複製構造函數直接建立對象,但編譯器沒有義務這樣作。blog

一般直接初始化和複製初始化僅在低級別上存在差別。
然而,對於不支持複製的類型,或者使用非 explicit 構造函數的時候,它們有本質區別:ci

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");

ifstream 類定義了一個可用 C 風格字符串調用的構造函數,使用該構造函數初始化 file1。資源

看上去等效的 file2 初始化使用複製初始化,但該定義不正確。
因爲不能複製 IO 類型的對象,因此不能對那些類型的對象使用複製初始化。

item 的初始化是否正確,取決於正在使用哪一個版本的 Sales_item 類。
某些版本將參數爲一個 string 的構造函數定義爲 explicit。
若是構造函數是顯式的,則初始化失敗;若是構造函數不是顯式的,則初始化成功。

形參與返回值

正如咱們所知,當形參爲非引用類型的時候,將複製實參的值。
相似地,以非引用類型做返回值時,將返回 return 語句 中的值的副本。
當形參或返回值爲類類型時,由複製構造函數進行復制。例如,考慮以前寫過的 make_plural 函數:

// copy constructor used to copy the return value;
// parameters are references, so they aren't copied
string make_plural(size_t, const string&, const string&);

這個函數隱式使用 string 複製構造函數返回給定單詞的複數形式。形參是 const 引用,不能複製。

初始化容器元素

複製構造函數可用於初始化順序容器中的元素。
例如,能夠用表示容量的單個形參來初始化容器。
容器的這種構造方式使用默認構造函數和複製構造函數:

// default string constructor and five string copy constructors invoked
vector<string> svec(5);

編譯器首先使用 string 默認構造函數建立一個臨時值來初始化 svec,
而後使用複製構造函數將臨時值複製到 svec 的每一個元素。
做爲通常規則,除非你想使用容器元素的默認初始值,
更有效的辦法是,分配一個空容器並將已知元素的值加入容器。

構造函數與數組元素

若是沒有爲類類型數組提供元素初始化式,則將用默認構造函數初始化每一個元素。
然而,若是使用常規的花括號括住的數組初始化列表來提供顯式元素初始化式,則使用複製初始化來初始化每一個元素。
根據指定值建立適當類型的元素,而後用複製構造函數將該值複製到相應元素:

Sales_item primer_eds[] = { string("0-201-16487-6"),
                string("0-201-54848-8"),
                string("0-201-82470-1"),
                Sales_item()
               };

如前三個元素的初始化式中所示能夠直接指定一個值,用於調用元素類型的單實參構造函數。
若是但願不指定實參或指定多個實參,就須要使用完整的構造函數語法,正如最後一個元素的初始化那樣。


1. 合成的複製構造函數

若是咱們沒有定義複製構造函數,編譯器就會爲咱們合成一個。
與合成的默認構造函數不一樣,即便咱們定義了其餘構造函數,也會合成複製構造函數。

合成複製構造函數的行爲是,執行逐個成員初始化,將新對象初始化爲原對象的副本。
所謂「逐個成員」,指的是編譯器將如今對象的每一個非 static 成員,依次複製到正建立的對象。
只有一個例外,每一個成員類型決定了複製該成員的含義。

合成複製構造函數直接複製內置類型成員的值,類類型成員使用該類的複製構造函數進行復制。
數組成員的複製是個例外。
雖然通常不能複製數組,但若是一個類具備數組成員,則合成複製構造函數將複製數組。
複製數組時合成複製構造函數將複製數組的每個元素。

逐個成員初始化最簡單的概念模型是,將合成複製構造函數看做這樣一個構造函數:
其中每一個數據成員在構造函數初始化列表中進行初始化。

例如,對於咱們的 Sales_item 類,它有三個數據成員:

class Sales_item {
// other members and constructors as before
private:
  std::string isbn;
  int units_sold;
  double revenue;
};

// 合成複製構造函數以下所示:
Sales_item::Sales_item(const Sales_item &orig):
        isbn(orig.isbn), // uses string copy constructor
        units_sold(orig.units_sold), // copies orig.units_sold
        revenue(orig.revenue) // copy orig.revenue
        { } // empty body

 

2. 定義本身的複製構造函數

複製構造函數就是接受單個類類型引用形參(一般用 const 修飾)的構造函數:

class Foo {
public:
  Foo(); // default constructor
  Foo(const Foo&); // copy constructor
  // ...
};

雖然也能夠定義接受非 const 引用的複製構造函數,但形參一般是一個 const 引用。
由於用於向函數傳遞對象和從函數返回對象,該構造函數通常不該設置爲 explicit。
複製構造函數應將實參的成員複製到正在構造的對象。

對許多類而言,合成複製構造函數只完成必要的工做。
只包含類類型成員或內置類型(但不是指針類型)成員的類,無須顯式地定義複製構造函數,也能夠複製。

然而,有些類必須對複製對象時發生的事情加以控制。
這樣的類常常有一個數據成員是指針,或者有成員表示在構造函數中分配的其餘資源。
而另外一些類在建立新對象時必須作一些特定工做。這兩種狀況下,都必須定義複製構造函數。

一般,定義複製構造函數最困難的部分在於認識到須要複製構造函數。
只要能認識到須要複製構造函數,定義構造函數通常很是簡單。
複製構造函數的定義與其餘構造函數同樣:它與類同名,沒有返回值,
能夠(並且應該)使用構造函數初始化列表初始化新建立對象的成員,能夠在函數體中作任何其餘必要工做。

後續章節中將給出一些須要定義複製構造函數的類的例子。


3. 禁止複製

有些類須要徹底禁止複製。例如,iostream 類就不容許複製。
若是想要禁止複製,彷佛能夠省略複製構造函數,然而,若是不定義複製構造函數,編譯器將合成一個。
爲了防止複製,類必須顯式聲明其複製構造函數爲 private。

若是複製構造函數是私有的,將不容許用戶代碼複製該類類型的對象,編譯器將拒絕任何進行復制的嘗試。

然而,類的友元和成員仍能夠進行復制。
若是想要連友元和成員中的複製也禁止,就能夠聲明一個(private)複製構造函數但不對其定義

聲明而不定義成員函數是合法的,可是,使用未定義成員的任未嘗試將致使連接失敗。
經過聲明(但不定義)private 複製構造函數,能夠禁止任何複製類類型對象的嘗試:
用戶代碼中複製嘗試將在編譯時標記爲錯誤,而成員函數和友元中的複製嘗試將在連接時致使錯誤。

不定義複製構造函數和/或默認構造函數,會嚴重侷限類的使用。
不容許複製的類對象只能做爲引用傳遞給函數或從函數返回,它們也不能用做容器的元素。

通常來講,最好顯式或隱式定義默認構造函數和複製構造函數。只有不存在其餘構造函數時才合成默認構造函數。若是定義了複製構造函數,也必須定義默認構造函數。

相關文章
相關標籤/搜索