C++複製控制:拷貝建構函式

QingLiXueShi發表於2015-01-23

一、拷貝建構函式是一種特殊建構函式,具有單個形參,該形參(常用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)如果定義了拷貝建構函式,也必須定義預設建構函式。不允許複製的類物件只能作為引用傳遞給函式或從函式返回,它們也不能作為容器元素,嚴重侷限類的使用。

相關文章