刪除函數(shù)的函數(shù)可以不進行實現(xiàn),默認函數(shù)就則不同:編譯器會創(chuàng)建函數(shù)實現(xiàn),通常都是“默認”實現(xiàn)。當然,這些函數(shù)可以直接使用(它們都會自動生成):默認構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),移動構(gòu)造函數(shù),拷貝賦值操作符和移動賦值操作符。
為什么要這樣做呢?這里列出一些原因:
改變函數(shù)的可訪問性——編譯器生成的默認函數(shù)通常都是聲明為public(如果想讓其為protected或private成員,必須自己實現(xiàn))。將其聲明為默認,可以讓編譯器來幫助你實現(xiàn)函數(shù)和改變訪問級別。
作為文檔——編譯器生成版本已經(jīng)足夠使用,那么顯式聲明就利于其他人閱讀這段代碼,會讓代碼結(jié)構(gòu)看起來很清晰。
沒有單獨實現(xiàn)的時候,編譯器自動生成函數(shù)——通常默認構(gòu)造函數(shù)來做這件事,如果用戶沒有定義構(gòu)造函數(shù),編譯器將會生成一個。當需要自定一個拷貝構(gòu)造函數(shù)時(假設(shè)),如果將其聲明為默認,也可以獲得編譯器為你實現(xiàn)的拷貝構(gòu)造函數(shù)。
編譯器生成虛析構(gòu)函數(shù)。
聲明一個特殊版本的拷貝構(gòu)造函數(shù),比如:參數(shù)類型是非const引用,而不是const引用。
就像刪除函數(shù)是在函數(shù)后面添加= delete一樣,默認函數(shù)需要在函數(shù)后面添加= default,例如:
class Y
{
private:
Y() = default; // 改變訪問級別
public:
Y(Y&) = default; // 以非const引用作為參數(shù)
T& operator=(const Y&) = default; // 作為文檔的形式,聲明為默認函數(shù)
protected:
virtual ~Y() = default; // 改變訪問級別,以及添加虛函數(shù)標簽
};
編譯器生成函數(shù)都有獨特的特性,這是用戶定義版本所不具備的。最大的區(qū)別就是編譯器生成的函數(shù)都很簡單。
列出了幾點重要的特性:
對象具有簡單的拷貝構(gòu)造函數(shù),拷貝賦值操作符和析構(gòu)函數(shù),都能通過memcpy或memmove進行拷貝。
字面類型用于constexpr函數(shù)(可見A.4節(jié)),必須有簡單的構(gòu)造,拷貝構(gòu)造和析構(gòu)函數(shù)。
類的默認構(gòu)造,拷貝,拷貝賦值操作符合析構(gòu)函數(shù),也可以用在一個已有構(gòu)造和析構(gòu)函數(shù)(用戶定義)的聯(lián)合體內(nèi)。
std::atomic<>類型模板(見5.2.6節(jié)),為某種類型的值提供原子操作。僅添加= default不會讓函數(shù)變得簡單——如果類還支持其他相關(guān)標準的函數(shù),那這個函數(shù)就是簡單的——不過,用戶顯式的實現(xiàn)就不會讓這些函數(shù)變簡單。
第二個區(qū)別,編譯器生成函數(shù)和用戶提供的函數(shù)等價,也就是類中無用戶提供的構(gòu)造函數(shù)可以看作為一個aggregate,并且可以通過聚合初始化函數(shù)進行初始化:
struct aggregate
{
aggregate() = default;
aggregate(aggregate const&) = default;
int a;
double b;
};
aggregate x={42,3.141};
例子中,x.a被42初始化,x.b被3.141初始化。
第三個區(qū)別,編譯器生成的函數(shù)只適用于構(gòu)造函數(shù);換句話說,只適用于符合某些標準的默認構(gòu)造函數(shù)。
struct X
{
int a;
};
如果創(chuàng)建了一個X的實例(未初始化),其中int(a)將會被默認初始化。
如果對象有靜態(tài)存儲過程,那么a將會被初始化為0;另外,當a沒賦值的時候,其不定值可能會觸發(fā)未定義行為:
X x1; // x1.a的值不明確
另外,當使用顯示調(diào)用構(gòu)造函數(shù)的方式對X進行初始化,a就會被初始化為0:
X x2 = X(); // x2.a == 0
這種奇怪的屬性會擴展到基礎(chǔ)類和成員函數(shù)中。當類的默認構(gòu)造函數(shù)是由編譯器提供,并且一些數(shù)據(jù)成員和基類都是有編譯器提供默認構(gòu)造函數(shù)時,還有基類的數(shù)據(jù)成員和該類中的數(shù)據(jù)成員都是內(nèi)置類型的時候,其值要不就是不確定的,要不就是被初始化為0(與默認構(gòu)造函數(shù)是否能被顯式調(diào)用有關(guān))。
雖然這條規(guī)則令人困惑,并且容易造成錯誤,不過也很有用;當你編寫構(gòu)造函數(shù)的時候,就不會用到這個特性;數(shù)據(jù)成員,通常都可以被初始化(指定了一個值或調(diào)用了顯式構(gòu)造函數(shù)),或不會被初始化(因為不需要):
X::X():a(){} // a == 0
X::X():a(42){} // a == 42
X::X(){} // 1
第三個例子中①,省略了對a的初始化,X中a就是一個未被初始化的非靜態(tài)實例,初始化的X實例都會有靜態(tài)存儲過程。
通常的情況下,如果寫了其他構(gòu)造函數(shù),編譯器就不會生成默認構(gòu)造函數(shù)。所以,想要自己寫一個的時候,就意味著你放棄了這種奇怪的初始化特性。不過,將構(gòu)造函數(shù)顯示聲明成默認,就能強制編譯器為你生成一個默認構(gòu)造函數(shù),并且剛才說的那種特性會保留:
X::X() = default; // 應(yīng)用默認初始化規(guī)則
這種特性用于原子變量(見5.2節(jié)),默認構(gòu)造函數(shù)顯式為默認。初始值通常都沒有定義,除非具有(a)一個靜態(tài)存儲的過程(靜態(tài)初始化為0),(b)顯式調(diào)用默認構(gòu)造函數(shù),將成員初始化為0,(c)指定一個特殊的值。注意,這種情況下的原子變量,為允許靜態(tài)初始化過程,構(gòu)造函數(shù)會通過一個聲明為constexpr(見A.4節(jié))的值為原子變量進行初始化。