不久前,在部落格上發表了一篇文章——
提高程式執行效率的10個簡單方法
,對於其中最後一點,多使用直接初始化,有很多讀者向我提出了疑問,並寫了一些測試程式,來說明直接初始化與複製初始化是同一件事。讓我了解到大家對於直接初始化與複製初始化的區別的確是不太清楚,無可否認,那篇文章的例子用得的確不太好,在這裡表示歉意!所以我覺得還是有必要跟大家詳細分享一下我對直接初始化和複製初始化的理解。
一、primer中的說法
首先我們來看看經典是怎麼說的:
「當用於類型別物件時,初始化的複製形式和直接形式有所不同:直接初始化直接呼叫與實參匹配的建構函式,複製初始化總是呼叫複製建構函式。複製初始化首先使用指定建構函式建立乙個臨時物件,然後用複製建構函式將那個臨時物件複製到正在建立的物件」
還有一段這樣說,
「通常直接初始化和複製初始化僅在低級別優化上存在差異,然而,對於不支援複製的型別,或者使用非explicit建構函式的時候,它們有本質區別:
ifstream file1("filename")://ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private 」
二、通常的誤解
從上面的說法中,我們可以知道,直接初始化不一定要呼叫複製建構函式,而複製初始化一定要呼叫複製建構函式。然而大多數人卻認為,直接初始化是構造物件時要呼叫複製建構函式,而複製初始化是構造物件時要呼叫賦值操作函式(operator=),其實這是一大誤解。因為只有物件被建立才會出現初始化,而賦值操作並不應用於物件的建立過程中,且primer也沒有這樣的說法。至於為什麼會出現這個誤解,可能是因為複製初始化的寫法中存在等號(=)吧。
為了把問題說清楚,還是從**上來解釋比較容易讓人明白,請看下面的**:
[cpp]view plain
copy
?
#include
#include
using
namespace std;
class classtest
classtest& operator=(const classtest &ct)
classtest(const
char *pc)
// private:
classtest(const classtest& ct)
private:
char c[256];
};
int main()
#include #include using namespace std;
class classtest
int main()
然而你還是非常遺憾地發現,還是沒有編譯通過。這是為什麼呢?從上面的語句和之前的執行結果來看,的確是已經沒有呼叫複製建構函式了,為什麼還是編譯錯誤呢?
經過實驗,main函式只有這樣才能通過編譯:
[cpp]view plain
copy
?
int main()
int main()
在這裡我們可以看到,原來是複製建構函式欺騙了我們。
四、揭開真相
看到這裡,你可能已經大驚失色,下面就讓我來揭開這個真相吧!
還是那一句,什麼是直接初始化,而什麼又是複製初始化呢?
簡單點來說,就是定義物件時的寫法不一樣,乙個用括號,如classtest ct1("ab"),而乙個用等號,如classtest ct2 = "ab"。
但是從本質來說,它們卻有本質的不同:直接初始化直接呼叫與實參匹配的建構函式,複製初始化總是呼叫複製建構函式。複製初始化首先使用指定建構函式建立乙個臨時物件,然後用複製建構函式將那個臨時物件複製到正在建立的物件。所以當複製建構函式被宣告為私有時,所有的複製初始化都不能使用。
現在我們再來看回main函式中的語句,
1、classtest ct1("ab");這條語句屬於直接初始化,它不需要呼叫複製建構函式,直接呼叫建構函式classtest(const char *pc),所以當複製建構函式變為私有時,它還是能直接執行的。
2、classtest ct2 = "ab";這條語句為複製初始化,它首先呼叫建構函式classtest(const char *pc)函式建立乙個臨時物件,然後呼叫複製建構函式,把這個臨時物件作為引數,構造物件ct2;所以當複製建構函式變為私有時,該語句不能編譯通過。
3、classtest ct3 = ct1;這條語句為複製初始化,因為ct1本來已經存在,所以不需要呼叫相關的建構函式,而直接呼叫複製建構函式,把它值複製給物件ct3;所以當複製建構函式變為私有時,該語句不能編譯通過。
4、classtest ct4(ct1);這條語句為直接初始化,因為ct1本來已經存在,直接呼叫複製建構函式,生成物件ct3的副本物件ct4。所以當複製建構函式變為私有時,該語句不能編譯通過。
注:第4個物件ct4與第3個物件ct3的建立所呼叫的函式是一樣的,但是本人卻認為,呼叫複製函式的原因卻有所不同。因為直接初始化是根據引數來呼叫建構函式的,如classtest ct4(ct1),它是根據括號中的引數(乙個本類的物件),來直接確定為呼叫複製建構函式classtest(const classtest& ct),這跟函式過載時,會根據函式呼叫時的引數來呼叫相應的函式是乙個道理;而對於ct3則不同,它的呼叫並不是像ct4時那樣,是根據引數來確定要呼叫複製建構函式的,它只是因為初始化必然要呼叫複製建構函式而已。它理應要建立乙個臨時物件,但只是這個物件卻已經存在,所以就省去了這一步,然後直接呼叫複製建構函式,因為複製初始化必然要呼叫複製建構函式,所以ct3的建立仍是複製初始化。
5、classtest ct5 = classtest();這條語句為複製初始化,首先呼叫預設建構函式產生乙個臨時物件,然後呼叫複製建構函式,把這個臨時物件作為引數,構造物件ct5。所以當複製建構函式變為私有時,該語句不能編譯通過。
五、假象產生的原因
產生上面的執行結果的主要原因在於編譯器的優化,而為什麼把複製建構函式宣告為私有(private)就能把這個假象去掉呢?主要是因為複製建構函式是可以由編譯預設合成的,而且是公有的(public),編譯器就是根據這個特性來對**進行優化的。然而如里你自己定義這個複製建構函式,編譯則不會自動生成,雖然編譯不會自動生成,但是如果你自己定義的複製建構函式仍是公有的話,編譯還是會為你做同樣的優化。然而當它是私有成員時,編譯器就會有很不同的舉動,因為你明確地告訴了編譯器,你明確地拒絕了物件之間的複製操作,所以它也就不會幫你做之前所做的優化,你的**的本來面目就出來了。
舉個例子來說,就像下面的語句:
classtest ct2 = "ab";
它本來是要這樣來構造物件的:首先呼叫建構函式classtest(const char *pc)函式建立乙個臨時物件,然後呼叫複製建構函式,把這個臨時物件作為引數,構造物件ct2。然而編譯也發現,複製建構函式是公有的,即你明確地告訴了編譯器,你允許物件之間的複製,而且此時它發現可以通過直接呼叫過載的建構函式classtest(const char *pc)來直接初始化物件,而達到相同的效果,所以就把這條語句優化為classtest ct2("ab")。
而如果把複製建構函式宣告為私有的,則物件之前的複製不能進行,即不能把臨時對像作為引數,呼叫複製建構函式,所以編譯就認為classtest ct2 = "ab"與classtest ct2("ab")是不等價的,也就不會幫你做這個優化,所以編譯出錯了。
注:根據上面的**,有些人可能會執行出與本人測試不一樣的結果,這是為什麼呢?就像前面所說的那樣,編譯器會為**做一定的優化,但是不同的編譯器所作的優化的方案卻可能有所不同,所以當你使用不同的編譯器時,由於這些優化的方案不一樣,可能會產生不同的結果,我這裡用的是g++4.7。
直接初始化與拷貝 複製 初始化
認識這兩種初始化有助於我們加深對語言的理解,可以更好的優化 我們常見的幾種初始化的形式 string str1 first 拷貝初始化,編譯器允許把這句話改寫為string str first 但是string 類必須有 public 的拷貝 移動 建構函式 string str2 10,a 直接初...
直接初始化與拷貝 複製 初始化
std set和std map都有乙個insert和emplace成員函式,那麼,他們的區別是什麼呢?他們都往 set或map 裡增加乙個元素,區別在於新元素的構造上。emplace 使用直接構造,insert 使用複製 拷貝 構造。那麼直接構造和複製構造有什麼區別呢?認識這兩種初始化 構造 有助於...
直接初始化和複製初始化
關於這個問題,國內外都有許多爭論,但我至今未找到滿意的答案,至於為為什麼,這就是今天要說明的。可能大家都有看過c primer,我看的是第四版,中文版,英文水平不怎麼樣。其中第13章,描述說,複製建構函式可用於 1.根據另乙個同型別的物件顯式或隱式初始化乙個物件 2.複製乙個物件,將它作為引數傳遞給...