共用方式為


明確的預設和被刪除的函式

在 C++11 中,預設和刪除的函式會讓您明確控制是否自動產生特殊成員函式。 刪除的函式也提供您簡單語言,防止有問題的型別提升,為特殊成員函式的函式,會在任何型別的引數間發生,以及會導致不必要函式呼叫的一般成員函式和非成員。

明確指定預設的和刪除的函式的優點

在 C++ 中,如果類型未自動宣告,編譯器會自動產生預設建構函式、複製建構函式、複製指派運算子和解構函式。 這些函式稱為特殊成員函式,就是它們使得 C++ 的簡單使用者定義類型的行為如同 C 的結構。 也就是說,您可以進行建立、複製和終結,而不需要任何額外的編碼作業。 C++11 在編譯器可以自動產生的特殊成員函式清單,並加入移動建構函式和移動-指派運算子呈現語言的移動語意。

這對於簡單類型很方便,但是複雜類型本身通常會定義一或多個特殊成員函式,因此這可能會防止其他特殊成員函式自動產生。 實際上:

  • 如果明確宣告任何建構函式,則不會自動產生任何預設建構函式。

  • 如果明確宣告建構函式,則不會自動產生任何預設解構函式。

  • 如果已明確宣告移動建構函式或移動指派運算子,則:

    • 不會自動產生複製建構函式。

    • 不會自動產生複製-指派運算子。

  • 如果複製建構函式、複製指派運算子、移動建構函式、移動指派運算子或解構函式宣告,則:

    • 不會自動產生移動建構函式。

    • 不會自動產生移動-指派運算子。

注意事項注意事項

此外, C++11 標準指定下列各項規則:

  • 如果複製建構函式或解構函式宣告,則取代自動產生的複製指派運算子。

  • 如果明確宣告複製指派運算子或解構函式,則取代自動產生的複製建函式。

在這兩種情況下, Visual Studio 會繼續自動產生隱含的必要函式,且不會發出警告。

這些規則結果,也可能遺漏至輸入物件的階層架構。 例如,如果基底類別因故無法取得衍生類別可呼叫的預設建構函式,也就是說,public 或 protected 無法取得任何參數,則建構函式會從無法自動產生預設建構函式中,衍生其類別。

這些規則會使原本簡單、使用者定義類型和共用 C++ 慣用語的實作複雜化。例如,透過私下宣告複製建構函式和複製指派運算子,而不加以定義,讓使用者定義類型不可複製。

struct noncopyable
{
  noncopyable() {};
  
private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

在 C++11 前,這個程式碼片段是不可複製類型的慣用形式。 不過,它有幾個問題:

  • 複製建構函式必須宣告為私用以便隱藏,但畢竟已宣告,因此會防止預設建構函式的自動產生。 您必須明確定義預設建構函式 (如果您需要),即使它沒有任何作用。

  • 即使明確定義的預設建構函式未執行任何動作,編譯器仍會將它視為非一般。 其效率比自動產生的預設建構函式還要低,但會防止 noncopyable 項目變成真正的 POD 類型。

  • 即使外部程式碼看不見複製建構函式和複製指派運算子,noncopyable 的成員項目仍可以看見及呼叫它們。 如果它們都已宣告但未定義,則呼叫它們會造成連結器錯誤。

  • 雖然這是一個廣為接受的方法,但除非您了解自動產生特殊成員函式的所有規則,否則目的並不明確。

在 C++11 中,non-copyable 慣用語可以用更直接的方式來實作。

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

請注意 C++11 以前的慣用語問題的解決方式:

  • 預設建構函式的產生仍是透過宣告複製的建構函式加以避免,但是您可以明確預設以再次產生預設建構函式。

  • 明確預設的特殊成員函式仍視為一般,因此不會對效能造成負面影響,而且不會防止 noncopyable 成為真正的 POD 類型。

  • 複製建構函式和複製指派運算子是公用的,但已刪除。 這是定義或呼叫已刪除函式的編譯時期錯誤。

  • 目的是要向所有了解 =default 和 =delete 的人進行釐清。 您不需要了解特殊成員函式自動產生的規則。

有類似的慣用語可供用來建立不可移動、只能動態配置,或者無法動態配置的使用者定義類型。 這些習慣用語都有遭遇類似問題預先 C++11 實作,且這些實作在 C++11 中會以根據預設的和刪除的特殊成員函式實作的類似方式解析。

明確預設的函式

可以預設任何特殊成員函式,以便明確指出特殊成員函式會使用預設的實作,以非公用存取限定詞來定義特殊成員函式,或者重新啟用自動產生被其他情況阻止的特殊成員函式。

如這個範例所示,宣告它以預設特殊成員函式:

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

請注意,您可以預設類別主體以外的特殊成員函式,只要此函式是可內嵌的。

因為一般特殊成員的效能優點,建議您在想要預設行為時選擇自動產生的特殊成員函式,而不要選擇空函式主體。 您可以明確預設特殊成員函式,或是不宣告它 (也不宣告會防止它自動產生的其他特殊成員函式),完成這項作業。

注意事項注意事項

Visual Studio 不支援以預設為基礎的移動建構函式或移動指派運算子,做為 C++11 標準命令。如需詳細資訊,請參閱 C++11 功能的支援 (現代 C++)中的<預設和刪除的函式>一節。

刪除的函式

您可以刪除特殊成員函式以及一般成員函式和非成員函式,以避免定義或呼叫它們。 刪除特殊成員函式可提供更簡潔的方式,防止編譯器產生不想要的特殊成員函式。 宣告後的函式必須刪除;預設並不會在函式宣告之後將它自動刪除。

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) =delete;
};

刪除一般成員函式或非成員函式可防止有問題的類型提升造成呼叫非預期的函式。 這個方法有效,因為刪除的函式仍然參與多載解析,而且比類型升級後可能被呼叫的函式提供更好的符合項目。 函式呼叫解析為更特定、但已刪除的函式,且造成編譯器的錯誤。

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

請注意,在上述範例中,使用 float 引數呼叫 call_with_true_double_only 會造成編譯器發生錯誤,但如果使用 int 引數呼叫 call_with_true_double_only 卻不會導致問題。在 int 中的情況為,引數由 int 晉升為 double,即使這不是一開始的目的,但卻成功呼叫 double 版本的函式。 您可以宣告刪除函式的樣版版本,以確定使用 non-double 引數呼叫的函式是否造成編譯器錯誤。

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.