c++ 11 中顯式預設設定的函式和已刪除的函式 總結
今天在一個類中看到如下程式碼不是很懂,原來是c++11 新特性
RateTimer(const RateTimer&) = delete; //不可拷貝/不可賦值
RateTimer& operator=(const RateTimer&) = delete;
在 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 中,不可複製的習語可通過更直接的方法實現。
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/14/17 功能中的“預設函式和已刪除的函式”一節。 |
已刪除的函式
可以刪除特殊成員函式以及普通成員函式和非成員函式,以阻止定義或呼叫它們。 通過刪除特殊成員函式,可以更簡潔地阻止編譯器生成不需要的特殊成員函式。 必須在宣告函式時將其刪除;不能在這之後通過宣告一個函式然後不再使用的方式來將其刪除。
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; }
請注意,在前面的示例中,使用 call_with_true_double_only 引數呼叫 float 將導致編譯器錯誤,但使用 call_with_true_double_only 引數呼叫 int 不會導致編譯器錯誤;在 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.
轉自 https://msdn.microsoft.com/zh-cn/library/dn457344.aspx