[英]Should we write `std::move` in the cases when RVO can not be done?
眾所周知, std::move
不應該應用於函數返回值,因為它可以防止 RVO(返回值優化)。 如果我們確實知道 RVO 不會發生,我對這個問題很感興趣。
這就是 C++14 標准所說的 [12.8/32]
當滿足復制/移動操作的省略條件但不滿足異常聲明時,並且要復制的對象由左值指定,或者當返回語句中的表達式是(可能是括號)id-使用在最內層封閉函數或 lambda 表達式的主體或參數聲明子句中聲明的自動存儲持續時間命名對象的表達式,首先執行為復制選擇構造函數的重載決策,就好像對象由右值指定一樣. 如果第一個重載決議失敗或未執行,或者如果所選構造函數的第一個參數的類型不是對對象類型的右值引用(可能是 cv 限定的),則再次執行重載決議,將對象視為左值。 [注意:無論是否會發生復制省略,都必須執行此兩階段重載解析。 它確定如果不執行省略則要調用的構造函數,並且即使調用被省略,所選構造函數也必須是可訪問的。 ——尾注]
這是《 Effective Modern C++
》一書中的解釋
標准支持 RVO 的部分繼續說,如果滿足 RVO 的條件,但編譯器選擇不執行復制省略,則返回的對象必須被視為右值。 實際上,標准要求當 RVO 被允許時,復制省略發生或 std::move 隱式應用於返回的本地對象
據我了解,當返回對象一開始不能被省略時,它應該被視為rvalue
。 在這些示例中,我們可以看到,當我們傳遞大於5
的參數時,對象會被移動,否則會被復制。 這是否意味着當我們知道 RVO 不會發生時,我們應該顯式編寫std::move
?
#include <iostream>
#include <string>
struct Test
{
Test() {}
Test(const Test& other)
{
std::cout << "Test(const Test&)" << std::endl;
}
Test(Test&& other)
{
std::cout << "Test(const Test&&)" << std::endl;
}
};
Test foo(int param)
{
Test test1;
Test test2;
return param > 5 ? std::move(test1) : test2;
}
int main()
{
Test res = foo(2);
}
該程序的輸出是Test(const Test&)
。
您的示例中發生的事情與RVO無關,而與三元operator ?
無關operator ?
。 如果使用if
語句重寫示例代碼,程序的行為將是預期的行為。 將foo
定義更改為:
Test foo(int param)
{
Test test1;
Test test2;
if (param > 5)
return std::move(test2);
else
return test1;
}
將輸出Test(Test&&)
。
如果你寫(param>5)?std::move(test1):test2
會發生什么(param>5)?std::move(test1):test2
是:
test2
通過lvalue-to-rvalue轉換,導致[expr.cond] / 6中所需的復制初始化 因此,在您的示例代碼中,移動elision發生,然而在形成三元運算符的結果所需的復制初始化之后。
實際上,在您的示例中,發生了復制省略。 如果您使用參數-fno-elide-constructors
明確禁止 RVO/NRVO,那么它可能會打印(我使用 Apple clang 版本 13.0.0 和 Homebrew GCC 11.2.0_2)
Test(const Test&)
Test(const Test&&)
Test(const Test&&)
調用第一個復制構造函數以評估表達式param > 5 ? std::move(test1) : test2
param > 5 ? std::move(test1) : test2
。 由於復制省略不可用,移動構造函數將至少被調用一次。 所以我認為在 return 語句上添加std::move
總是多余的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.