C++ の右辺値について混乱していたので、右辺値と右辺値参照についてメモしておく。
value category
C++ の式は、型とは別に value category というものを持つ。value category は prvalue, xvalue, lvalue のいずれかであり、prvalue と xvalue をまとめて 右辺値 と呼ぶ。(prvalue, xvalue, lvalue については後述)
注意すべき点として、
- 同じ型を持つ式でも、異なる value category を持つことがある。
- 同じオブジェクトを返す式でも、異なる value category を持つことがある。
例えば、
string s = string("hello ") + string("world");
strint t = s;
というコードにおいて、1行目の string("hello ") + string("world")
の型と、2行目の右辺の s
の型は同一(string
型)であるが、value category は異なっている (それぞれ prvalue と lvalue)。
ひどい例として、以下のような場合、
string&& r = string("hello");
string t = r;
r
の型は string
への右辺値参照だが、2行目の r
という式の value category は lvalue になる。
左辺値の式から無理やり右辺値を作りたい場合は std::move
関数を使ってキャストする。
string s("hello");
string&& r = move(s); // move(s) は xvalue (右辺値の一種)
関数のオーバーロードと右辺値
string hoge = "piyo";
string x = ???;
という式を考える。
???
の部分が hoge
のような左辺値だった場合、string のコピーコンストラクタが呼び出される。一方、???
の部分が move(hoge)
のような右辺値の式だった場合、string のムーブコンストラクタが呼び出される。
一般に、ある関数 f
が「左辺値参照を取るバージョン」と「右辺値参照を取るバージョン」の2通りにオーバーロードされていて、かつ f
の引数が右辺値の式になっている場合、右辺値参照を取るバージョンの f
が呼び出される。コピーコンストラクタ vs ムーブコンストラクタの例は、このルールのスペシャルケースになっている。
prvalue, xvalue, lvalue について
prvalue
prvalue (pure rvalue) は一時オブジェクトを生じさせる式。
例:
- 文字列以外のリテラル (
100
とかnullptr
とか) - 戻り値の型が参照型でない関数呼び出し式
- 組み込みの算術式、論理式、比較式など
xvalue
xvalue (eXpiring value) は move 元のオブジェクトを表す式。
例:
- 戻り値の型が右辺値参照である関数呼び出し
- 右辺値参照へのキャスト式
- xvalue である式の non-static クラスメンバへのアクセス式
lvalue
lvalue は一時的でないオブジェクトを表す式。
例:
- 変数名
- 戻り値の方が左辺値参照である関数呼び出し
- 代入式
- 文字列リテラル