一、拷貝建構函式是一種特殊建構函式,具有單個形參,該形參(常用const修飾)是對該類型別的引用。與預設建構函式一樣 ,拷貝建構函式可由編譯器隱式呼叫。拷貝建構函式應用的場合為:
(1)根據另一個同型別的物件顯式或隱式初始化一個物件。
(2)複製一個物件將它作為實參傳給一個函式。
(3)從函式返回時複製一個物件。
(4)初始化順序容器中的元素。
(5)根據元素初始化式列表初始化陣列元素。
下面分別對以上5點進行說明。
1、物件的定義式。
C++支援兩種初始化形式:直接初始化和複製初始化。複製初始化使用“=”符號,而直接初始化將初始化式放在圓括號中。對於類型別物件,初始化的複製形式和直接形式有所不同。
直接初始化直接呼叫與實參匹配的建構函式。複製初始化首先使用指定建構函式建立一個臨時物件,然後用拷貝建構函式將那個臨時物件複製到正在建立的物件。
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
說明:
(1)對於類型別物件,只有指定單個實參或顯式建立一個臨時物件用於複製時,才使用複製初始化。
(2)支援初始化的複製形式主要是為了與C的用法相容。當情況許可時,可以允許編譯器跳過複製建構函式直接建立物件,但編譯器沒有義務這樣做。
(3)對於不支援複製的型別,或者使用explicit建構函式,不能進行復制初始化。例如:由於不能複製IO型別的物件,所以不能對那些型別的物件使用複製初始化。
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");
2、形參與返回值。
當形參為非引用型別時,將複製實參的值,以非引用型別作返回值時,將返回return語句中值的副本。因而,當形參或返回值為類型別時,由拷貝建構函式進行復制。
string make_plural(size_t, const string &, const string &);
這個函式隱式使用string拷貝建構函式返回值的副本,形參是const引用,不會複製。
3、初始化容器元素。
vector<string> vec(5);
使用了預設建構函式和拷貝建構函式。編譯器首先使用string預設建構函式建立一個臨時物件,然後使用拷貝建構函式將臨時值複製到vec的每個元素。
4、建構函式與陣列元素。
當用花括號初始化列表來顯式初始化類型別的陣列,則使用複製初始化來初始化每個元素。根據指定值建立適當型別元素,然後用拷貝建構函式將該值複製到相應元素。
Sales_item primer_eds[] = { string("0-201-16487-6"), string("0-201-54848-8"), string("0-201-82470-1"), Sales_item() };
當定義一個新物件並用一個同型別的物件對它初始化時,將顯式使用拷貝建構函式。當將該型別的物件傳遞給函式或從函式返回該型別的物件時,將隱式使用拷貝建構函式。
二、合成的拷貝建構函式。
如果沒有定義拷貝建構函式,編譯器就會為我們合成一個。與合成的預設建構函式不同,即使我們定義了其他建構函式,也會合成拷貝建構函式。合成拷貝建構函式的行為是,執行逐個成員初始化,將新物件初始化為原物件的副本。
說明:
(1)編譯器將現在物件的每個非static成員,依次複製到正建立的物件。每個成員的型別決定了複製該成員的含義。
(2)合成拷貝建構函式直接複製內建型別成員的值,類型別成員使用該類的拷貝建構函式進行復制。
(3)陣列成員的複製是個例外。雖然一般不能複製陣列,但如果一個類具有陣列成員,則合成複製建構函式將複製陣列的每一個元素。
Sales_item::Sales_item(const Sales_item &orig): isbn(orig.isbn), //使用string拷貝建構函式 units_sold(orig.units_sold), //直接複製orig.units_sold revenue(orig.revenue) //直接複製orig.revenue { }
三、定義自己的拷貝建構函式。
拷貝建構函式就是接受單個類型別引用形參(通常用const修飾)的建構函式。因為用於向函式傳遞物件和從函式返回物件,該建構函式一般不應設定為explicit,拷貝建構函式應將實參的成員複製到正在構造的物件。它與類同名,沒有返回值,可以(而且應該)使用建構函式初始化列表初始化新建立物件的成員,可以在函式體中做任何其他必要工作。
說明:
(1)合成拷貝建構函式只完成必要的工作。只包含類型別成員或內建型別(但不是指標型別)成員的類,無須顯式地定義拷貝建構函式,也可以複製。
(2)有些類必須定義複製建構函式對複製物件時發生的事情加以控制。例如:
1)類有一個資料成員是指標,或者有成員表示在建構函式中分配的其他資源
2)類在建立新物件時必須做一些特定工作。
四、禁止複製。
有些類需要完全禁止複製,例如:iostream。如何禁止複製呢?省略拷貝建構函式這種做法不行,因為編譯器將會幫我們合成一個。為了防止複製,類必須顯式宣告其拷貝建構函式為private。
說明:
(1)如果拷貝建構函式是私有的,將不允許使用者複製該型別物件。
(2)類的友元和成員仍可以進行復制,如果也想禁止它們,可宣告一個私有的拷貝建構函式但不對其定義。宣告而不定義是合法的,但使用未定義的成員將導致連結失敗。
(3)通過宣告不定義私有拷貝建構函式,可禁止任何複製類型別物件的嘗試:使用者嘗試複製導致編譯錯誤,成員函式和友元複製導致連結錯誤。
(4)如果定義了拷貝建構函式,也必須定義預設建構函式。不允許複製的類物件只能作為引用傳遞給函式或從函式返回,它們也不能作為容器元素,嚴重侷限類的使用。