左辺値参照の取得と右辺値参照の取得const
は、2つの異なるものです。
類似点:
- どちらも参照であるため、コピーや移動は発生しません。参照はオブジェクトを参照するだけであり、オブジェクトをコピー/移動することはありません。
違い:
いくつかの例を見てみましょう:
const
左辺値の参照を取る:void f(const T& t);
左辺値を渡す:
T t; f(t);
t
これはオブジェクトの名前であるため、左辺値式です。左辺値参照はconst
何にでもバインドできるためt
、参照によって渡されます。何もコピーされず、何も移動されません。
右辺値を渡す:
f(T());
これT()
は一時オブジェクトを作成するため、右辺値式です。繰り返しますが、const
左辺値参照は何にでもバインドできるので、これは問題ありません。何もコピーされず、何も移動されません。
どちらの場合も、t
関数の内部は渡されたオブジェクトへの参照です。参照isによって変更することはできませんconst
。
右辺値の参照を取る: `void f(T && t);
左辺値を渡す:
T t;
f(t);
これにより、コンパイラエラーが発生します。右辺値参照は左辺値にバインドされません。
右辺値を渡す:
f(T());
右辺値参照は右辺値にバインドできるため、これは問題ありません。関数内の参照t
は、によって作成された一時オブジェクトを参照しますT()
。
では、考えてみましょうstd::move
。まず最初に:std::move
実際には何も動かしません。アイデアは、それに左辺値を与えて、それを右辺値に変えるというものです。それがすべてです。したがってf
、右辺値参照を取得する場合は、次のようにすることができます。
T t;
f(std::move(t));
これt
は、左辺値ですstd::move(t)
が、右辺値であるために機能します。これで、右辺値参照をバインドできます。
では、なぜ右辺値参照引数を取るのでしょうか。実際、移動コンストラクターと代入演算子を定義する場合を除いて、頻繁に行う必要はありません。const
右辺値参照を受け取る関数を定義するときはいつでも、ほぼ確実に左辺値参照のオーバーロードを与えたいと思うでしょう。ほとんどの場合、ペアで提供する必要があります。
void f(const T&);
void f(T&&);
この関数のペアが役立つのはなぜですか?最初の値は、左辺値(またはconst
右辺値)を指定するたびに呼び出され、2番目の値は、変更可能な右辺値を指定するたびに呼び出されます。通常、右辺値を受け取るということは、一時オブジェクトが与えられたことを意味します。これは、オブジェクトが長く存在しないことがわかっているという事実に基づいて、その内部を破壊し、最適化を実行できることを意味するため、すばらしいニュースです。
したがって、この関数のペアを使用すると、一時オブジェクトを取得していることがわかっているときに最適化を行うことができます。
この関数のペアの非常に一般的な例があります。コピーコンストラクターと移動コンストラクターです。それらは通常、次のように定義されます。
T::T(const T&); // Copy constructor
T::T(T&&); // Move constructor
したがって、移動コンストラクターは、実際には、一時オブジェクトを受け取るときに最適化された単なるコピーコンストラクターです。
もちろん、渡されるオブジェクトは必ずしも一時的なオブジェクトではありません。上で示したように、を使用std::move
して左辺値を右辺値に変換できます。次に、それは関数の一時オブジェクトのように見えます。基本的に使用std::move
すると、「このオブジェクトを一時オブジェクトとして扱うことができます」と言います。それが実際に移動するかどうかは関係ありません。
ただし、コピーコンストラクターと移動コンストラクターを作成する以外に、この関数のペアを使用するのには十分な理由があります。オブジェクトを受け取り、一時オブジェクトであるかどうかに関係なく、オブジェクトとまったく同じように動作する関数を作成している場合は、そのオブジェクトを値で取得するだけです。検討:
void f(T t);
T t;
f(t);
f(T());
への最初の呼び出しf
では、左辺値を渡します。それが関数にコピーされます。への2番目の呼び出しf
では、右辺値を渡します。そのオブジェクトは関数に移動されます。参照してください-オブジェクトを効率的に移動させるために右辺値参照を使用する必要さえありませんでした。私たちはそれを価値あるものとしてとらえました!なんで?コピー/移動を行うために使用されるコンストラクターは、式が左辺値であるか右辺値であるかに基づいて選択されるためです。コピー/移動コンストラクターに任せてください。
異なる引数タイプが同じコードになるかどうかについては、まったく別の質問です。コンパイラは、as-ifルールの下で動作します。これは単に、プログラムが標準の指示どおりに動作する限り、コンパイラーが好きなコードを出力できることを意味します。したがって、関数がまったく同じことを行う場合、関数は同じコードを出力する可能性があります。またはそうでないかもしれません。ただし、定数左辺値参照と右辺値参照を受け取る関数が同じことをしている場合は、悪い兆候です。