複製省略
當滿足某些條件時,可以省略從同類型(忽略 cv-限定符)源物件建立類物件的過程,即使所選的建構函式和/或解構函式具有副作用。這種省略物件建立的過程稱為 副本消除。
目錄 |
[編輯] 解釋
副本消除在以下情況下是被允許的(可以組合以消除多個副本):
- 在具有類返回型別的函式中的 return 語句中,當運算元是非 volatile 且具有 自動儲存期 的物件 obj 的名稱時(函式引數或 處理函式 引數除外),可以透過將 obj 直接構造到函式呼叫的結果物件中來省略結果物件的 複製初始化。這種副本消除的變體稱為 命名返回值最佳化 (NRVO)。
|
(C++17 前) |
(C++11 起) | |
|
(C++20 起) |
當發生副本消除時,實現將省略初始化的源和目標視為引用同一物件的兩種不同方式。
銷燬發生在沒有最佳化的情況下,這兩個物件本應被銷燬的時間中較晚的一個。 |
(C++11 前) |
如果所選建構函式的第一個引數是對物件型別的右值引用,則該物件的銷燬發生在目標本應被銷燬時。否則,銷燬發生在沒有最佳化的情況下,這兩個物件本應被銷燬的時間中較晚的一個。 |
(C++11 起) |
純右值語義(“保證的副本消除”)自 C++17 起,純右值在需要時才例項化,然後直接構造到其最終目的地的儲存中。這有時意味著即使語言語法在視覺上暗示複製/移動(例如 複製初始化),也沒有執行復制/移動——這意味著該型別根本不需要可訪問的複製/移動建構函式。示例包括: T f() { return U(); // constructs a temporary of type U, // then initializes the returned T from the temporary } T g() { return T(); // constructs the returned T directly; no move }
T x = T(T(f())); // x is initialized by the result of f() directly; no move
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // no elision when initializing a base class subobject D(int) : D(g()) {} // no elision because the D object being initialized might // be a base-class subobject of some other class }; 注意:此規則未指定最佳化,標準也未正式將其描述為“副本消除”(因為沒有內容被消除)。相反,C++17 核心語言對 純右值 和 臨時物件 的規範與早期 C++ 版本根本不同:不再有臨時物件可供複製/移動。描述 C++17 機制的另一種方式是“未例項化值傳遞”或“延遲臨時例項化”:純右值在不例項化臨時物件的情況下返回和使用。 |
(C++17 起) |
[編輯] 注意
副本消除是 唯一允許的最佳化形式(C++14 前) 兩種允許的最佳化形式之一,與 分配消除和擴充套件 並列,(C++14 起) 可以改變可觀察的副作用。由於某些編譯器在允許的所有情況下都不執行副本消除(例如,在除錯模式下),因此依賴於複製/移動建構函式和解構函式的副作用的程式不可移植。
在 return 語句或 throw 表示式中,如果編譯器無法執行副本消除但滿足副本消除的條件,或者除了源是函式引數之外,其他條件都滿足,編譯器將嘗試使用移動建構函式,即使源運算元由左值指定(C++23 前) 源運算元將被視為右值(C++23 起);詳見 return 語句。 struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(C++11 起) |
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_guaranteed_copy_elision |
201606L |
(C++17) | 透過簡化的 值類別 保證副本消除 |
[編輯] 示例
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (until C++17) copy elision initializing v from a temporary; // the move constructor may be called // (since C++17) "guaranteed copy elision" return v; // copy elision ("NRVO") from v to the result object; // the move constructor may be called } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (until C++17) copy elision initializing v from the result of f() // (since C++17) "guaranteed copy elision" std::cout << "&v = " << &v << '\n'; g(f()); // (until C++17) copy elision initializing arg from the result of f() // (since C++17) "guaranteed copy elision" }
可能的輸出
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1967 | C++11 | 當使用移動建構函式完成副本消除時, 被移動物件的生命週期仍被考慮在內。 |
不考慮 |
CWG 2426 | C++17 | 返回純右值時不需要解構函式 | 解構函式可能被呼叫 |
CWG 2930 | C++98 | 只能消除複製(/移動)操作,但 複製初始化可以選擇非複製(/移動)建構函式 |
消除任何物件構造 相關複製初始化 |