C++においてlvalue(左辺値)は、メモリ内のオブジェクトを参照する式であり、名前があり参照できるもの(例えば変数)です。rvalue(右辺値)は、一時的な値であり名前を持たず、従来の意味でのオブジェクトではありません(例えば、a + bの結果や、リテラルの5など)。
lvalueはアドレスを取ることができますが、rvalueは取ることができません(rvalue参照が登場する前は)。関数に渡す方法には次のものがあります:
void foo(const std::string& s); — lvalueとrvalueを受け取ります。void bar(std::string& s); — lvalueのみを受け取ります。void baz(std::string&& s); — rvalueのみを受け取ります。例:
void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue
rvalue参照は、一時オブジェクトの効率的な受け渡しのために必要で、主にリソースを移動させるためのムーブセマンティクスに使用され、コピーするのではなくリソースを移動します。
std::move(obj)の式はどのタイプの参照(lvalueまたはrvalue)を取得しますか?std::moveを適用した後、オブジェクトはどのカテゴリになりますか?
答え:
std::move(obj)は常にrvalue参照(T&&)を返しますが、オブジェクト自体はlvalueのままであり、単に明示的な変換が適用されるだけです。この後、オブジェクトには非常に注意して対処する必要があります(未定義ですが有効な状態にある可能性があります)。
例:
std::string s = "data"; std::string d = std::move(s); // dはsのデータを受け取りますが、sは空になります
事例
大規模なプロジェクトで、ある開発者が一時オブジェクトをlvalue参照T&で渡していました(T&&またはconst T&の代わりに)。これによりコンパイルエラーが発生し、最適でないコピーが行われました—ムーブセマンティクスが使用されず、パフォーマンスが40%低下しました。
事例
フロントエンドエンジンで、std::moveを変数に誤って適用し、その後再び使用されることがありました。その結果、変数は「破壊された」状態になり、クラッシュやレンダースレッドの異常終了が発生しました。
事例
シリアル化ライブラリで、std::vector<T>のコンテナを関数にlvalueとして渡しましたが、移動を期待していました。移動の代わりに、多くの要素を高価にコピーすることになり、大きな配列のシリアル化時間が急激に悪化しました。