c++ - 戻り値 - rvalue reference




オーバーロードされた算術における移動意味論および右辺値参照渡し (2)

DavidRodríguez氏には、非会員operator+関数を使用する方が良い設計であると同意しますが、それを無視してあなたの質問に集中します。

私はあなたが書くときにパフォーマンスの低下が見られることに驚きました

T operator+(const T&)
{
  T result(*this);
  return result;
}

の代わりに

T operator+(const T&)
{
  T result(*this);
  return std::move(result);
}

前者の場合、コンパイラはRVOを使用して、関数の戻り値のメモリにresultを構築できるはずです。 後者の場合、コンパイラはresultを関数の戻り値に移動する必要があるため、移動に余分なコストがかかります。

一般に、この種のことのための規則は、あなたがオブジェクトを返す関数を持っていると仮定している(すなわち、参照ではない)。

  • ローカルオブジェクトまたは値によるパラメータを返す場合は、 std::moveを適用しないでください。 これにより、コンパイラはRVOを実行できます。これはコピーや移動よりも安価です。
  • rvalue参照型のパラメータを返す場合は、それにstd::moveを適用します。 それはパラメータを右辺値に変え、それゆえコンパイラはそこから移動することができます。 単にパラメータを返す場合、コンパイラは戻り値へのコピーを実行する必要があります。
  • 普遍的な参照(すなわち、右辺値参照または左辺値参照である可能性がある推定型の " && "パラメータ)であるパラメータを返す場合は、それにstd::forwardを適用します。 それがないと、コンパイラーは戻り値へのコピーを実行しなければなりません。 これにより、参照が右辺値にバインドされている場合、コンパイラーは移動を実行できます。

私は小さな数値解析ライブラリをC ++でコーディングしています。 私は移動意味論を含む最新のC ++ 11機能を使って実装しようとしています。 私は次の投稿で議論とトップアンサーを理解しています: C ++ 11右辺値と移動意味論の混乱(return文) 、しかし私はまだ頭を包み込もうとしている一つのシナリオがあります。

私はクラスを持っています、それをTと呼びます。 コピーと移動の両方のコンストラクタもあります。

T (const T &) { /*initialization via copy*/; }
T (T &&) { /*initialization via move*/; }

私のクライアントコードは演算子を多用しているので、私は複雑な算術式が移動の意味論から最大の利益を得ることを確実にしようとしています。 次の点を考慮してください。

T a, b, c, d, e;
T f = a + b * c - d / e;

Move Semanticsがなければ、私のオペレータは毎回コピーコンストラクタを使って新しいローカル変数を作成しているので、全部で4つのコピーがあります。 私は、移動の意味論を使えば、これを2部コピーといくつかの移動に減らすことができると望んでいました。 かっこで囲まれたバージョンでは:

T f = a + (b * c) - (d / e);

(b * c)(d / e)は通常の方法でコピーを作成して一時的なものを作成しなければなりませんが、移動だけで残りの結果を累積するためにこれらの一時的なものを利用できれば素晴らしいでしょう。

g ++コンパイラを使って、私はこれをすることができました、しかし、私は私の技術が安全でないかもしれないと思う、そして、私は完全に理由を理解したいです。

加算演算子の実装例は次のとおりです。

T operator+ (T const& x) const
{
    T result(*this);
    // logic to perform addition here using result as the target
    return std::move(result);
}
T operator+ (T&& x) const
{
    // logic to perform addition here using x as the target
    return std::move(x);
}

std::move呼び出さないと、各演算子のconst &バージョンのみが呼び出されます。 しかし、上記のようにstd::moveを使用する場合、後続の算術演算(最も内側の式の後)は各演算子の&&バージョンを使用して実行されます。

私はRVOを抑制することができることを知っていますが、非常に計算量が多く、現実の問題では、RVOの欠如よりも利益がわずかに大きいように思えます。 つまり、 std::moveを含めると、何百万もの計算にわたって非常に小さなスピードアップが得られます。 正直言ってそれなしで十分速いです。 ここでは意味を完全に理解したいだけです。

私のstd :: moveの使用がここでは悪いことであるのかどうか、またその理由を説明するために時間を割いても構わないと思っている親切なC ++ Guruはいますか? 事前に感謝します。


あなたは完全な型対称性を得るために自由関数として演算子をオーバーロードすることを好むべきです(同じ変換が左右で適用されることができます)。 それはあなたが質問から抜けているものをもう少し明白にします。 あなたが提供している無料の機能としてあなたのオペレータを言い直す:

T operator+( T const &, T const & );
T operator+( T const &, T&& );

しかし、あなたは左側が一時的であることを扱うバージョンを提供することに失敗しています:

T operator+( T&&, T const& );

両方の引数が右辺値であるときにコードのあいまいさを避けるためには、さらにもう1つのオーバーロードを提供する必要があります。

T operator+( T&&, T&& );

一般的なアドバイスは、現在のオブジェクトを変更するメンバメソッドとして+=を実装してから、インターフェイス内の適切なオブジェクトを変更するフォワーダとしてoperator+を書くことです。

私は実際にはあまり考えていませんでしたが、 T (r / l値参照なし)を使用する代替策があるかもしれませんが、 operator+をすべての状況で効率的にするために必要なオーバーロードの数を減らすことはないでしょう。







move-semantics