C++11 明確地控制預設函式:delete 與 default

| | 0 Comments| 09:56
Categories:

在 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 函式,避免被外部呼叫到。下面就是一個例子:

class NonCopyable
{
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 在宣告函式的時候,加入了 defaultdelete 這兩個語法,可以用來針對函式做進一步的控制。

以上面要建立一個不可以被複製的類別的例子來說,在 C 11 可以寫成:

class NonCopyable
{
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 當作參數,但是當我們把 intdouble 等其他型別的數值當作參數來傳遞的時候,編譯器是會自動去找合適的方法,來做型別轉換的。

void testFunc( float fVal ){}

int main()
{
    double d = 1;
    int i = 1;

    testFunc(d);
    testFunc(i);
}

那如果我們希望 testFunc() 很明確地、只能接受 float 當參數該怎麼辦呢?MSDN 提供的方法,就是在上面的程式裡面,加入下面這個 template 函式、並且把他設定為 delete

template<typename T>
void testFunc(T) = delete;

如此一來,編譯器就會只允許有明確定義的型別當作參數了(這邊就是 float)~而當要把其他型別的資料當作參數傳給 testFunc() 的時候,也不會有自動型別轉換的動作,而是會出現編譯階段的錯誤。

另外,如果想避免自定義的類別被用 new 來做動態配置,也可以把 operator new(參考)給刪除,讓他無使用;下面就是一個例子:

class testClass
{
public:
    void* operator new( std::size_t ) = delete;
};

而如果不希望被用 new 來產生陣列的話,則應該也可以靠刪除 operator new[]參考)來做到。


參考:明確的預設和被刪除的函式

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *