共用方式為


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

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

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

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

這對簡單類型而言十分方便,但是複雜類型通常會自行定義一個或多個特殊成員函式,而且這可以防止自動產生其他特殊成員函式。 實際上:

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

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

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

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

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

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

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

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

注意

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

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

在這兩種情況下,Visual Studio 會繼續以隱含方式自動產生必要的函式,而且預設不會發出警告。 由於 Visual Studio 2022 17.7 版, C5267 可以啟用發出警告。

這些規則的結果也可能滲入物件階層架構中。 例如,如果基類因為任何原因而無法從衍生類別呼叫的預設建構函式,也就是 public 不接受任何參數的 或 protected 建構函式,則衍生自它的類別無法自動產生自己的預設建構函式。

這些規則可讓實作直接、使用者定義型別和常見的 C++ 慣用語複雜化,例如,藉由私下宣告複製建構函式和複製指派運算子,使使用者定義型別不可複製。

struct noncopyable
{
  noncopyable() {};

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

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

  • 複製建構函式必須私下宣告以隱藏它,但由於它完全宣告,因此無法自動產生預設建構函式。 如果您需要預設建構函式,則必須明確地定義預設建構函式,即使它不執行任何動作也是一樣。

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

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

  • 雖然這是常用的慣用語,但除非您瞭解自動產生特殊成員函式的所有規則,否則意圖並不清楚。

在 C++11 中,可以更直接的方式實作不可複製的語式。

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;

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

因為一般特殊成員函式的效能優勢,建議您在想要預設行為時偏好使用透過空白函式主體自動產生的特殊成員函式。 做法是明確預設特殊成員函式,或不予宣告 (也不宣告會防止自動產生它的其他特殊成員函式)。

已刪除的函式

您可以刪除特殊成員函式和一般成員函式和非成員函式,以防止定義或呼叫它們。 刪除特殊成員函式提供更簡潔的方式,防止編譯器產生您不想要的特殊成員函式。 函式必須在宣告時刪除;之後無法刪除函式,然後預設為函式。

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 函式的版本,即使這可能不是您想要的。 若要確保使用非雙精度引數對這個函式的任何呼叫都會導致編譯器錯誤,您可以宣告已刪除函式的範本版本。

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.