名稱空間
變體
操作

複製省略

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
表示式
替代表示
字面量
布林字面量 - 整數字面量 - 浮點字面量
字元字面量 - 字串字面量 - nullptr (C++11)
使用者定義 (C++11)
工具
屬性 (C++11)
型別
typedef 宣告
類型別名宣告 (C++11)
型別轉換
記憶體分配
類特有的函式屬性
explicit (C++11)
static

特殊成員函式
模板
雜項
 
 

當滿足某些條件時,可以省略從同類型(忽略 cv-限定符)源物件建立類物件的過程,即使所選的建構函式和/或解構函式具有副作用。這種省略物件建立的過程稱為 副本消除

目錄

[編輯] 解釋

副本消除在以下情況下是被允許的(可以組合以消除多個副本):

  • 在具有類返回型別的函式中的 return 語句中,當運算元是非 volatile 且具有 自動儲存期 的物件 obj 的名稱時(函式引數或 處理函式 引數除外),可以透過將 obj 直接構造到函式呼叫的結果物件中來省略結果物件的 複製初始化。這種副本消除的變體稱為 命名返回值最佳化 (NRVO)。
  • 當類物件 target 使用尚未繫結到引用的臨時類物件 obj 進行復制初始化時,可以透過將 obj 直接構造到 target 中來省略複製初始化。這種副本消除的變體稱為 未命名返回值最佳化 (URVO)。自 C++17 起,URVO 是強制性的,不再被視為副本消除的一種形式;詳見下文。
(C++17 前)
  • throw 表示式中,當運算元是非 volatile 且具有自動儲存期的物件 obj 的名稱時(函式引數或處理函式引數除外),該物件屬於不包含最內層 try(如果存在)的 作用域,可以透過將 obj 直接構造到異常物件中來省略異常物件的複製初始化。
  • 處理函式中,如果程式的含義不變,除了處理函式引數的建構函式和解構函式的執行,可以透過將處理函式引數視為異常物件的別名來省略處理函式引數的複製初始化。
(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 物件被銷燬,返回型別的解構函式也必須在 return 語句處可訪問且未被刪除。
  • 在物件初始化中,當初始化表示式是與變數型別相同的類型別(忽略 cv-限定符)的 純右值時。
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 只能消除複製(/移動)操作,但
複製初始化可以選擇非複製(/移動)建構函式
消除任何物件構造
相關複製初始化

[編輯] 另見