保證副本省略

Version >= C++ 17

通常,elision 是一種優化。雖然在最簡單的情況下幾乎每個編譯器都支援複製省略,但是 elision 仍然給使用者帶來了特別的負擔。也就是說,被刪除的複製/移動型別必須仍然具有被刪除的複製/移動操作。

例如:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  return std::lock_guard<std::mutex>(a_mutex);
}

如果 a_mutex 是某個系統私有的互斥鎖,但外部使用者可能希望對其進行範圍鎖定,則這可能很有用。

這也不合法,因為 std::lock_guard 無法複製或移動。儘管幾乎每個 C++編譯器都會忽略複製/移動,但標準仍然需要該型別才能使該操作可用。

直到 C++ 17。

C++ 17 通過有效地重新定義某些表示式的含義來強制執行,從而不會發生複製/移動。考慮上面的程式碼。

在 pre-C++ 17 的措辭下,該程式碼表示​​建立一個臨時的然後使用臨時複製/移動到返回值,但臨時副本可以省略。根據 C++ 17 的措辭,這根本不會產生臨時性。

在 C++ 17 中,任何 prvalue 表示式在用於初始化與表示式相同型別的物件時都不會生成臨時表示式。表示式直接初始化該物件。如果返回與返回值相同型別的 prvalue,則該型別不需要具有複製/移動建構函式。因此,根據 C++ 17 規則,上述程式碼可以工作。

在 prvalue 的型別與正在初始化的型別匹配的情況下,C++ 17 的措辭有效。所以給定 get_lock,這也不需要複製/移動:

std::lock_guard the_lock = get_lock();

由於 get_lock 的結果是用於初始化相同型別的物件的 prvalue 表示式,因此不會發生複製或移動。那種表達永遠不會造成暫時的; 它用於直接初始化 the_lock。沒有任何缺點,因為沒有副本/移動被省略。

因此,保證副本省略這個術語有點用詞不當,但這就是為 C++ 17 標準化提出的功能名稱 。它根本不保證省略; 它完全消除了複製/移動,重新定義了 C++,因此從未有過複製/移動。

此功能僅適用於涉及 prvalue 表示式的情況。因此,這使用通常的省略規則:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  std::lock_guard<std::mutex> my_lock(a_mutex);
  //Do stuff
  return my_lock;
}

雖然這是複製省略的有效案例,但在這種情況下,C++ 17 規則並不能消除複製/移動。因此,型別必須仍具有用於初始化返回值的複製/移動建構函式。由於 lock_guard 沒有,這仍然是編譯錯誤。允許實現在傳遞或返回普通可複製型別的物件時拒絕刪除副本。這是為了允許在暫存器中移動這些物件,一些 ABI 可能在其呼叫約定中強制要求。

struct trivially_copyable {
    int a;  
};

void foo (trivially_copyable a) {}

foo(trivially_copyable{}); //copy elision not mandated