在 C 03 的時候,如果程式開發者自己定義一個新的類別的話,就算在什麼都沒有寫的情況下,編譯器也會自動產生一些預設的函式,這些函式包括了:
-
預設建構函式(default constructor)
sampleClass() -
複製建構函式(copy constructor)
sampleClass( const sampleClass& ) -
複製指派運算子(copy assignment operator)
sampleClass& operator= ( const sampleClass& ) -
解構函式(destructor)
~sampleClass()
而在一般狀況下,除非自己去實作同類型的函式,否則這些預設的函式,都是會被編譯器自動產生的。所以,我們定義的類別才可以很方便地直接被建構、刪除、複製。
比如說,當定義了一個類別後,又不希望這個類別可以被用預設的 copy constructor 與 copy assignment operator 複製的話,就必須要自己去定義這兩個函式;而通常如果不希望這個被別物件可以被複製的話,大多就是把這兩個函式定義成 private 函式,避免被外部呼叫到。下面就是一個例子:
{
public:
NonCopyable(){};
private:
NonCopyable(const NonCopyable&);
NonCopyable& operator=(const NonCopyable&);
};
這樣在某種程度上,就可以避免  NonCopyable 這個類別的物件,可以被直接複製了~
但是實際上,這個方法除了在寫法上的語意沒這麼明確外,還有兩個問題:
-
雖然外部會因為 copy constructor 與 copy assignment operator 屬於 private 函式而無法使用,但是 NonCopyable 本身還是可以使用。
-
由於定義了 copy constructor,所以編譯器不會產生 default constructor,必須自己定義。而一般來說,自定義的 default constructor 效率會比編譯器自行產生的差,而且會讓類別無法變成 POD 的結構(Plain old data structure)。
而為了解決這個問題,C 11 在宣告函式的時候,加入了 default 和 delete 這兩個語法,可以用來針對函式做進一步的控制。
以上面要建立一個不可以被複製的類別的例子來說,在 C 11 可以寫成:
{
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
在上面的例子裡,程式的寫法是變成:
-
在 copy constructor 與 copy assignment operator 的宣告後面都加上了「= delete」,藉此讓編譯器知道這兩個函式是不需要的,之後如果呼叫的話,就會產生編譯階段的錯誤。
-
在 default constructor 後面加上了「= default」,事告訴編譯器這邊雖然重新宣告了 default constructor,但是還是要使用編譯器預設產生的版本。
透過這樣的寫法,就可以避免舊寫法的缺點了~
而除了可以用在類別的特殊函式上,delete 也還有其他的用途。其中一個特殊用途,就是防止編譯器幫忙做函式參數的自動轉型。
比如說在下面的程式碼裡面,雖然這邊定義的 testFunc() 是需要使用 float 當作參數,但是當我們把 int、double 等其他型別的數值當作參數來傳遞的時候,編譯器是會自動去找合適的方法,來做型別轉換的。
int main()
{
double d = 1;
int i = 1;
testFunc(d);
testFunc(i);
}
那如果我們希望 testFunc() 很明確地、只能接受 float 當參數該怎麼辦呢?MSDN 提供的方法,就是在上面的程式裡面,加入下面這個 template 函式、並且把他設定為 delete。
void testFunc(T) = delete;
如此一來,編譯器就會只允許有明確定義的型別當作參數了(這邊就是 float)~而當要把其他型別的資料當作參數傳給 testFunc() 的時候,也不會有自動型別轉換的動作,而是會出現編譯階段的錯誤。
另外,如果想避免自定義的類別被用 new 來做動態配置,也可以把 operator new(參考)給刪除,讓他無使用;下面就是一個例子:
{
public:
void* operator new( std::size_t ) = delete;
};
而如果不希望被用 new 來產生陣列的話,則應該也可以靠刪除 operator new[](參考)來做到。
參考:明確的預設和被刪除的函式