2017-06-10 6 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は、1回だけ構築され、破壊されます。 TestTMはネストされたNRVOのケースです。再びオブジェクトは一度だけ作成され、コピーも移動もされません。ここまでは順調ですね。

今、私の質問に - TestT1TestTMと同じ効率で動作させるにはどうしたらいいですか? TestT1に見られるように、2番目のオブジェクトが移動されました。これは避けたいものです。どのように追加のコピーや移動を避けるために機能transform1()を変更することができますか?あなたがそれについて考えるなら、TestT1TestTMと大きく異なるわけではないので、これが可能でなければならないと感じています。

私の2回目の試行では、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で発生します。これは、コンパイラが、関数の引数リストのvalueパラメータからNRVOを明示的に適用できないためです。そしてTest1では、関数パラメータとして値によってWインスタンスを受け入れているので、コンパイラは戻り時にその移動をエリートできません。

は、あなたがこのため、以前の場合に行ったようにあなたは同様に効率的Test1仕事をすることはできませんWhy are by-value parameters excluded from NRVO?を参照してくださいとコメント

でここWhy does for_each return function by move問題についてハワードヒナントと私の議論。


標準

15.8.3コピー/移動エリジオンから関連引用[class.copy.elision]

特定の基準が満たされ
  1. 、実装クラスオブジェクトのコピー/移動の構築を省略することができます...クラス戻り型を持つ関数でreturn文の

    • 発現関数パラメータまたは例外宣言によって導入された変数以外の非揮発性自動オブジェクトの名前(ありますハンドラ(18.3)の)関数の戻り型と同じ型(CV-資格を無視して)で、コピー/移動操作は、関数呼び出しの戻りオブジェクト
  2. に直接自動オブジェクトを構築することによって省略することができます
+0

ありがとう、私は理解していると思います。しかし、なぜ 'TestT2'は動作しませんか?私はその参照が一時的なオブジェクトの存続期間を延ばすと思った? – Barnett

+0

@Barnett参照にバインドされたオブジェクトの存続期間は、バインドされるオブジェクトが完全なオブジェクト(ほとんどの場合、その値がprvalueの場合)または完全なオブジェクトの完全なサブオブジェクト(たとえば、auto && val =何か{}。member_variable')。 'TestT2'の' && 'パラメータと戻り値は、式の存続期間を超えてバインドされた一時的なものの存続期間を延長しません関数呼び出しは – Curious

+0

に表示されます。両方の答えはhttps://stackoverflow.com/questions/42441791/lifetime-extension-prvalues-and-xvaluesで確認できます。 – Curious

関連する問題