bugfix> c++ > 投稿

私はC ++ 17標準によって保証された寿命を理解しようとしています、特に保証された 省略をコピーします。 例から始めましょう

std::string make_tmp();
std::string foo() {
   return std::string{make_tmp().c_str()};
}

何が起こっているのかわからないmake_tmp 一時的な string を作成します t を呼び出します ; foo (不要に作成された)一時的な( t のコピーを返します の c_str )。 標準(C ++ 17以前でも)は t の有効期間を保証します完全なリターン式が評価されるまでの時間。 したがって、安全なので、 t の一時コピーを作成します(返される)。

今コピーの省略 キックイン;具体的には、最初のC ++ 17ブロックの2番目の箇条書き:

In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.

したがって、一時コピーはまったく作成されません。

フォローアップの質問:

  1. 返された一時コピーは、十分に長い t の有効期間を意味しますか-省略されることが保証されていても?

  2. foo のバリアントを検討する下記のとおり。 私は、コピーの省略はもはや必要ではないと仮定しています(しかし、かなり可能性が高い)。 コピーが省略されない場合、標準はカバーされます(上記の引数によって)。 コピーが省略された場合、標準は t の十分な寿命を保証しますか return のタイプにもかかわらず ed-expressionが foo と異なる の戻り値の型?

foo -バリアント:

std::string foo() {
   return make_tmp().c_str();
}

私は、この規格によって純粋に暗示されている保証を理解したいと思います。 注意してください、私は両方の foo バージョンは「動作」します(つまり、さまざまなコンパイラでカスタムクラスを使用してテストする場合でも、ダングリングポインターは含まれません)。

回答 3 件
  • この回答は、OPで求められた存続期間の問題に直接回答します(コピー除外とは無関係であることがわかります)。 returnステートメントの実行中に発生したストーリー全体に精通していない場合は、Barryの答えを参照できます。


    はい、一時ファイルは[stmt.return]/2ごとに返されたオブジェクトのコピー初期化中に持続することが保証されています。

    The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables ([stmt.jump]) of the block enclosing the return statement.

  • 私はここでいくつかの混乱があると思いますどれ コピーは省略されています。最も遠いビューを見てみましょう:

    std::string make_tmp();
    std::string foo() {
       return std::string{make_tmp().c_str()};
    }
    std::string s = foo();
    
    

    ここに、潜在的に 4つの std::string があります 作成された: make_tmp() 、一時的な std::string{...}  それから構築された、 foo() の戻りオブジェクト 、および s 。これは3つのコピーを意味します(これらすべてが移動であっても、一貫性のためにコピーという単語を使用します。これが混乱しないことを願っています)。

    コピー省略により、これらのコピーのうち2つを削除できます。

    std::string{...} からのコピー   foo() の戻りオブジェクトへ 。

    foo() からのコピー   s

    これらの省略はどちらもC ++ 17の「コピー保証の保証」で義務付けられています-prvalueから初期化するため(コピーを実行する必要があると判断するために実際にオーバーロード解決を実行していないという点で少し混乱する用語です)構築してからスキップするだけで、直接初期化しています)。コードは次と同じです:

    std::string s{make_tmp().c_str()};
    
    

    しかし、これは削除できません-私たちはまだ string を構築しています   make_tmp() 経由 、その内容を引き出してから、新しい string を構築します  それらから。それを回避する方法はありません。

    提供されたバリアントの動作はまったく同じです。

  • Does the returned temporary copy still imply a sufficiently extended lifetime of t -- even though it is guaranteed to be elided?

    t   foo になります の体、そして脱落は make_tmp で起こる の体。だから t の寿命はエリシオンの影響を受けません 一時的、静的、動的など何でもあります。

    foo

    Consider the variant of foo given below. I'm assuming, copy elision is no longer required (but rather highly likely). If the copy will not be elided, the standard's got us covered (by the arguments above). In case the copy is elided, does the standard still guarantee a sufficient lifetime of t despite the type of the returned-expression being different from foo's return type?

      make_tmp().c_str() と同等です  元のスニペットで、 std::string(make_tmp().c_str())  コンストラクターの呼び出しは暗黙的に行われます。投稿の冒頭で述べたように、脱落は起こります。

    省略の保証を理解するには、アセンブリレベルでリターンロジックがどのように機能するかを理解することをお勧めします。これにより、コンパイラが呼び出しのリターンメカニズムをどのように作成するかを理解できます。ここでの標準は、実際のコンパイラの実装に遅れずについていくためのもので、明快さを与え、新しい言語構文の概念を導入しています。

    簡単な例:

    std::string
    
    

    アセンブリでは、関連部分 std::string foo(); int main() { auto t = foo(); }  本体は次のようになります。

    main
    
    

    効果的に起こるのは、 0000000000400987 <main>: .... ; Allocate 32-byte space (the size of `std::string` on x64) on the stack ; for the return value 40098b: 48 83 ec 20 sub $0x20,%rsp ; Put the pointer of the stack allocated chunk to RAX 40098f: 48 8d 45 e0 lea -0x20(%rbp),%rax ; Move the pointer from RAX to RDI ; RDI - is a first argument location for a callee by the calling convention ; By calling convention, the return of not trivial types (`std::string` in our case) ; must be taken care on the caller side, it must allocate the space for the return type ; and give the pointer as a first argument (what of course, is hidden by the compiler ; for C/C++) 400993: 48 89 c7 mov %rax,%rdi ; make a call 400996: e8 5b ff ff ff callq 4008f6 <foo()> ; At this point you have the return value at the allocated address on the main's stack ; at RBP - 32 location. Do whatever further. ....  スペースはすでに呼び出し元の( t の)スタックとこのスタックメモリのアドレスが呼び出し先 main に渡されます 。 foo  ロジックを挿入するだけで、それで終わりです。 foo   foo を構築するためにメモリを割り当てる場合があります  そして、このメモリを指定されたメモリにコピーしますが、(多くの場合簡単な最適化である)何も割り当てずに、指定されたメモリで直接動作することもあります後者では、コンパイラはコピーコンストラクタを呼び出すかもしれませんが、意味がありません。 C ++ 17標準では、この事実が明確になりました。

    std::string

あなたの答え