1. ホーム
  2. c++

[解決済み] 値渡し vs rvalue参照渡し

2023-05-17 05:11:08

質問

関数はいつ宣言すればよいのでしょうか。

void foo(Widget w);

とは対照的に

void foo(Widget&& w); ?

これが唯一のオーバーロードであると仮定します(両方ではなく、どちらか一方を選び、他のオーバーロードはない、というように)。テンプレートは関係ありません。関数が foo の所有権が必要だとします。 Widget (例えば const Widget& はこの議論に含まれない)。このような状況の範囲外の回答には興味がありません。については、記事末尾の追記をご覧ください。 なぜ が質問の一部である理由については、投稿の最後にある補遺をご覧ください。

私の同僚と私が思いつく主な違いは、rvalue参照パラメータがコピーについて明示的であることを強制することです。呼び出し側は明示的にコピーを作成する責任があり、それを std::move で渡すことになります。値で渡すケースでは、コピーのコストは隠蔽されます。

    //If foo is a pass by value function, calling + making a copy:
    Widget x{};
    foo(x); //Implicit copy
    //Not shown: continues to use x locally

    //If foo is a pass by rvalue reference function, calling + making a copy:
    Widget x{};
    //foo(x); //This would be a compiler error
    auto copy = x; //Explicit copy
    foo(std::move(copy));
    //Not shown: continues to use x locally

その違い以外には コピーについて明示することを強制することと、関数を呼び出すときに得られる構文上の糖分を変更すること以外に、これらはどのように違うのでしょうか?インターフェイスについて何か違うことを言っているのでしょうか?これらは互いに効率的なのか、それともそうでないのか?

私と同僚がすでに考えている他のこと。

  • rvalue 参照パラメータというのは があります。 を意味し、それを強制するものではありません。呼び出し先で渡した引数が、その後元の状態になる可能性はあります。また、関数が移動コンストラクタを呼び出すことなく引数を食べたり変更したりする可能性もありますが、rvalue 参照であるため、呼び出し元が制御を放棄したと見なすことができます。値で渡す場合、それに移動する場合、移動が起こったと見なさなければなりません。 エリジオンを使用しないと仮定すると、1 つの移動コンストラクター呼び出しは、rvalue によるパスで排除されます。
  • コンパイラーは、値渡しでコピー/移動のエリジオンを行うより良い機会があります。この主張を実証できる人はいますか? できれば、標準の行ではなく、gcc/clang から最適化された生成コードを示す gcc.godbolt.org へのリンクがあると望ましいです。これを示そうとした私の試みは、おそらくうまく動作を分離することができなかったのでしょう。 https://godbolt.org/g/4yomtt

追記です。 なぜ 私はこの問題をそんなに制約しているのでしょうか?

  • オーバーロードなし - もし他のオーバーロードがあった場合、これは値による渡しと const 参照と rvalue 参照の両方を含むオーバーロードのセットとの議論に発展するでしょうが、その時点でオーバーロードのセットの方が明らかに効率的で勝利します。これはよく知られていることであり、したがって興味深いことではありません。
  • テンプレートがない - 転送参照がどのように適合するかには興味がありません。転送参照がある場合は、とにかく std::forward を呼び出します。転送参照でのゴールは、受け取ったものをそのまま渡すことです。コピーは関係なく、lvalueを渡すだけだからです。これはよく知られていることで、面白いことではありません。
  • foo の所有権を必要とします。 Widget (別名 const Widget& ) - 読み取り専用の関数について話しているのではありません。もし関数が読み取り専用であったり、所有したり寿命を延ばしたりする必要がない場合は Widget を所有または拡張する必要がない場合、答えは自明で const Widget& となり、これもまたよく知られていることで、面白みがありません。また、なぜ私たちがオーバーロードについて話したくないのかについても言及します。

どのように解決するのですか?

<ブロッククオート

rvalue参照パラメータは、コピーについて明示的にすることを強制します。

そうですね、pass-by-rvalue-referenceは一理ありますね。

<ブロッククオート

rvalue referenceパラメータは、引数を動かしてもよいことを意味しますが、それを強制するものではありません。

そう、pass-by-valueは一理ある。

しかし、それはまた、例外保証を処理する機会をpass-by-rvalueに与える:もし foo を投げます。 widget の値は消費される必要はありません。

移動のみの型(例えば std::unique_ptr のように、パス・バイ・バリューが一般的なようです(ほとんどは2番目のポイントについてで、1番目のポイントはいずれにせよ適用できません)。

EDIT: 標準ライブラリは私の前の文章と矛盾しており、そのうちの1つは shared_ptr のコンストラクタは std::unique_ptr<T, D>&& .

コピー/ムーブの両方を持つ型(例えば std::shared_ptr のように)、以前の型との整合性をとるか、コピー時に明示的であることを強制するかの選択を迫られます。

不要なコピーがないことを保証したいのでなければ、coherencyにpass-by-valueを使用することになるでしょう。

保証された、あるいは即時のシンクが必要でない限り、私ならpass-by-rvalueを使用します。

既存のコードベースでは、私なら一貫性を保つでしょう。