2017-06-10 72 views
4

我知道,NRVO允許一個函數構造一個對象,並返回該對象的價值沒有一個副本,甚至移動操作的成本。它發現它也可以與嵌套的函數調用一起使用,允許您從另一個函數調用的返回值構造對象。C++命名返回值優化與嵌套函數調用

請考慮下面的程序和它的輸出在評論中所示:(輸出從Visual Studio 2017年,15.2版,發行版本)

#include <stdio.h> 
class W 
{ 
public: 
    W() { printf("W::W()\n"); } 
    W(const W&) { printf("W::W(const W&)\n"); } 
    W(W&&) { printf("W::W(W&&)\n"); } 
    W& operator=(const W&) { printf("W::operator=(const W&)\n"); } 
    W& operator=(W&&) { printf("W::operator=(W&&)\n"); } 
    ~W() { printf("W::~W()\n"); } 
    void Transform() { printf("W::Transform()\n"); } 
    void Run() { printf("W::Run()\n"); } 
}; 

W make() 
{ 
    W w; 
    return w; 
} 

W transform_make() 
{ 
    W w{ make() }; 
    w.Transform(); 
    return w; 
} 

W transform1(W w) 
{ 
    w.Transform(); 
    return w; 
} 

W&& transform2(W&& w) 
{ 
    w.Transform(); 
    return std::move(w); 
} 

int main()       // Program output: 
{ 
    printf("TestM:\n");   //TestM: 
    {        //W::W() 
    W w{ make() };     //W::Run() 
    w.Run();      //W::~W() 
    } 
            //TestTM: 
    printf("TestTM:\n");   //W::W() 
    {        //W::Transform() 
    W w{ transform_make() };  //W::Run() 
    w.Run();      //W::~W() 
    } 
            //TestT1: 
    printf("TestT1:\n");   //W::W() 
    {        //W::Transform() 
    W w{ transform1(make()) }; //W::W(W&&) 
    w.Run();      //W::~W() 
    }        //W::Run() 
            //W::~W() 

    printf("TestT2:\n");   //TestT2: 
    {        //W::W() 
    W&& w{ transform2(make()) }; //W::Transform() 
    w.Run();      //W::~W() 
    }        //W::Run() 
} 

TestM是正常NRVO情況。對象W只構造並銷燬一次。 TestTM是嵌套的NRVO情況。該對象再次構造一次,從不復制或移動。到現在爲止還挺好。

現在我的問題 - 我怎樣才能使TestT1的工作效率與TestTM相同?正如您在TestT1中看到的,第二個對象是移動構建 - 這是我想避免的。我如何更改功能transform1()以避免任何額外的副本或移動?如果你仔細想想,TestT1TestTM沒什麼太大的區別,所以我覺得這是必須的。

對於我第二次嘗試TestT2,我嘗試通過RValue引用傳遞對象。這消除了額外的移動構造函數,但不幸的是這導致析構函數在我完成對象之前被調用,這並不總是理想的。

更新:
我還注意到,有可能使其工作使用的引用,只要確保不使用對象超出聲明的末尾:

W&& transform2(W&& w) 
{ 
    w.Transform(); 
    return std::move(w); 
} 

void run(W&& w) 
{ 
    w.Run(); 
} 

printf("TestT3:\n");   //TestT3: 
{        //W::W() 
    run(transform2(make())); //W::Transform() 
}        //W::Run() 
           //W::~W() 

是這安全嗎?

回答

1

這發生在Test1,因爲編譯器明確不允許通過函數的參數列表中的值參數應用NRVO。並且在Test1中,您正在接受按值作爲函數參數的W實例,因此編譯器無法避免返回時的移動。

Why are by-value parameters excluded from NRVO?與霍華德Hinnant(欣南特)關於這裏的問題Why does for_each return function by move在評論

我討論,你不能讓Test1工作儘可能有效地在你因爲這個早期的情況一樣。


從標準

15.8.3複製/移動省音的有關報價[class.copy.elision]

  1. 當滿足特定條件時,一種實現允許省略類對象的複製/移動構造,...

      在與類返回類型,功能的 return語句
    • 表達是非易失性自動對象的名稱(除功能參數或由異常聲明引入一個可變其他一個處理機(18.3))與相同類型的(忽略CV-資格)作爲函數返回類型時,複製/移動操作可以通過直接構建自動對象到函數調用的返回對象
  2. 被省略
+0

謝謝,我想我明白了。但爲什麼'TestT2'不工作?我認爲參考文獻會延長臨時對象的生命週期? – Barnett

+0

@Barnett綁定到引用的對象的生命週期僅在被綁定的對象是完整對象(對於大多數情況下,如果它是一個prvalue)或完整對象的完整子對象(例如'auto && val = Something {}。member_variable')。並且沒有終身的臨時對象延長了它們出現的整個表達式。因此,'TestT2'中的'&&'參數和返回值不會延長超出表達式生命週期的任何臨時對象的生命週期該函數調用出現在 – Curious

+0

也可以在這裏看到這兩個答案https://stackoverflow.com/questions/42441791/lifetime-extension-prvalues-and-xvalues,它們可能有助於解釋更多一點 – Curious