lvalues
は「保存場所が定義されたもの」であると読みました。
また、リテラルと一時変数は左辺値ではありませんが、このステートメントには理由がありません。
リテラルと一時変数に格納場所が定義されていないためですか?はいの場合、メモリにない場合、どこにありますか?
「定義済み保管場所」の「定義済み」にはいくつかの意味があると思いますが、ある場合(ない場合)にお知らせください。
また、リテラル変数と一時変数は左辺値ではありませんが、このステートメントには理由がありません。
これは、文字列リテラルを除くすべての一時変数とリテラルに当てはまります。これらは実際には左辺値です(これについては以下で説明します)。
リテラルと一時変数が定義された格納場所を持っていないためですか?はいの場合、メモリにない場合、それらはどこにありますか?
はい。リテラル_2
_は実際には存在しません。これはソースコードの単なる値です。これはオブジェクトではなく値であるため、メモリを関連付ける必要はありません。コンパイラが作成するアセンブリにハードコードすることも、どこかに置くこともできますが、そうする必要がないため、オブジェクトではなく純粋な値として扱うことができます。
ただし、免除があり、それは文字列リテラルです。文字列リテラルは_const char[N]
_の配列であるため、これらには実際にストレージがあります。文字列リテラルのアドレスを取得でき、文字列リテラルはポインタに減衰する可能性があるため、名前がなくても左辺値です。
一時変数も右辺値です。オブジェクトとして存在していても、保管場所は一時的なものです。彼らは彼らがいる完全な表現の終わりまでしか持続しません。あなたは彼らの住所を取ることは許されておらず、彼らには名前もありません。それらは存在しないかもしれません:たとえば、
_Foo a = Foo();
_
Foo()
は削除でき、コードは意味的に次のように変換されます。
_Foo a(); // you can't actually do this since it declares a function with that signature.
_
そのため、最適化されたコードには一時オブジェクトさえありません。
リテラルと一時変数が左辺値ではないのはなぜですか?
私には2つの答えがあります。それは意味をなさないから(1)、規格はそう言うから(2)。 (1)に注目しましょう。
リテラルと一時変数が定義された格納場所を持っていないためですか?
これは、ここに収まらない簡略化です。単純化:リテラルと一時変数は変更する意味がないため、左辺値ではありません。1。
5++
の意味は何ですか?Rand() = 0
?の意味は何ですか?規格では、一時変数とリテラルは左辺値ではないため、これらの例は無効です。そして、すべてのコンパイラ開発者は幸せです。
1) 一時的な変更が意味をなす方法で、ユーザー定義型を定義して使用できます。この一時的なものは、完全な式の評価まで存続します。 FrançoisAndrieuxは、一方でf(MyType{}.mutate())
を呼び出すことと、もう一方でf(my_int + 1)
を呼び出すことの類似点を示しています。 MyType{}.mutate()
はseen asのように、MyType{}
が一時的なものであり、my_int + 1
はseen as = my_int
と同じように、別のint
。これはすべてセマンティクスと意見に基づいています。本当の答えは次のとおりです:(2)標準がそう言っているからです。
質問と他の回答には、よくある誤解がたくさんあります。私の答えは、それに対処したいと考えています。
用語lvalueおよびrvalueは式のカテゴリ 。これらは表現に適用される用語です。オブジェクトではありません。 (少し紛らわしいことに、式カテゴリの公式用語は「値カテゴリ」です!)
一時オブジェクトという用語はオブジェクトを指します。これには、組み込み型のオブジェクトだけでなく、クラス型のオブジェクトも含まれます。 temporary(名詞として使用される)という用語は、temporary objectの略です。スタンドアロンの用語valueは、組み込み型の一時オブジェクトを参照するために使用されることがあります。これらの用語は、式ではなくオブジェクトに適用されます。
C++ 17標準は、オブジェクトの用語において、過去の標準よりも一貫しています。 [conv.rval]/1を参照してください。現在は、式のコンテキスト値以外ではvalueと言わないようにしています。
さて、なぜ異なる表現カテゴリがあるのですか? C++プログラムは式のコレクションで構成され、演算子で結合されてより大きな式を作成します。宣言的な構成のフレームワークに適合します。これらの式は、オブジェクトの作成、破棄、およびその他の操作を行います。 C++でのプログラミングは、式を使用してオブジェクトを操作するものとして説明できます。
式のカテゴリが存在するのは、プログラマーが意図する操作を式を使用して表現するためのフレームワークを提供するためです。たとえば、C時代(おそらくそれ以前)に戻ると、言語設計者は_3 = 5;
_がプログラムの一部としては意味をなさないと考えたため、左側に表示できる表現の種類を制限することにしました。 _=
_の一方で、この制限が守られなかった場合にコンパイラにエラーを報告させます。
lvalueという用語は、当時C++の開発に伴い、式の左側だけでなく、式のカテゴリが役立つ幅広い範囲の式とコンテキストがありますが、代入演算子。
有効なC++コードは次のとおりです:std::string("3") = std::string("5");
。これは概念的には_3 = 5;
_と同じですが、許可されています。その結果、タイプ_std::string
_およびコンテンツ_"3"
_の一時オブジェクトが作成され、その一時オブジェクトがコンテンツ_"5"
_を持つように変更され、一時オブジェクトが破棄されます。コードは、コード_3 = 5;
_が同様の一連のイベントを指定するように設計されている可能性があります(そうではありませんでした)。
string
の例が合法であるのに、int
の例が合法ではないのはなぜですか?
すべての式にはカテゴリが必要です。式のカテゴリには、最初は明らかな理由がないように見えるかもしれませんが、言語の設計者は、表現するのに役立つと思う概念とそうでない概念に従って、各式にカテゴリを割り当てています。
上記の_3 = 5;
_のイベントのシーケンスは、誰もがやりたいことではないことがわかっています。誰かがそのようなことを書いた場合、おそらく間違いを犯して別のことを意味しているので、コンパイラが役立つはずですエラーメッセージを表示して出力します。
さて、同じロジックは、std::string("3") = std::string("5")
は誰もがしたくないものだと結論付けるかもしれません。ただし、別の引数として、他のクラス型の場合、T(foo) = x;
は実際に価値のある操作になる場合があります。 T
は何かを行うデストラクタを持っている可能性があるためです。この使用を禁止することは、プログラマーの意図よりも害になる可能性があることが判明しました。 (それが良い決定であったかどうかは議論の余地があります; この質問を参照してください 議論のために)。
今、私たちは最終的にあなたの質問に取り組むために近づいています:)
メモリまたは保存場所が関連付けられているかどうかは、もはや表現カテゴリの根拠ではありません。 abstract machine(これについては以下で詳しく説明します)では、すべての一時オブジェクト(これには_3
_の_x = 3;
_によって作成されたオブジェクトが含まれます)がメモリに存在します。
前述のように、プログラムはオブジェクトを操作する式で構成されています。各式は、オブジェクトを指定するまたは参照すると言います。
このトピックに関する他の回答や記事では、右辺値は一時オブジェクトのみを指定できるという誤った主張をすることがよくあります。さらに悪いことに、右辺値は一時オブジェクトであるということですまたは、一時オブジェクトが右辺値であること。式はオブジェクトではなく、オブジェクトを操作するためのソースコードで発生するものです。
実際、一時オブジェクトは左辺値または右辺値の式で指定できます。非一時オブジェクトは、左辺値または右辺値の式で指定できます。それらは別の概念です。
これで、右辺値カテゴリの式に_&
_を適用できない式カテゴリルールがあります。このルールとこれらのカテゴリの目的は、一時オブジェクトが破棄された後に使用されるエラーを回避することです。例えば:
_int *p = &5; // not allowed due to category rules
*p = 6; // oops, dangling pointer
_
しかし、これを回避することができます:
_template<typename T> auto f(T&&t) -> T& { return t; }
// ...
int *p = f(5); // Allowed
*p = 6; // Oops, dangling pointer, no compiler error message.
_
この後者のコードでは、f(5)
および_*p
_はどちらも一時オブジェクトを指定する左辺値です。これは、式カテゴリルールが存在する理由の良い例です。トリッキーな回避策なしでルールに従うと、ぶら下がりポインタを介して書き込もうとするコードでエラーが発生します。
このf
を使用して、一時オブジェクトのメモリアドレスを見つけることもできます。 std::cout << &f(5);
要約すると、実際に尋ねる質問はすべて、誤って式をオブジェクトと混同します。したがって、それらはその意味では非質問です。オブジェクトは式ではないため、一時変数は左辺値ではありません。
有効だが関連する質問は次のとおりです。「一時オブジェクトを作成する式はなぜ右辺値ではなく右辺値ですか?」
答えは上記で説明したとおりです。それを左辺値にすると、ダングリングポインターまたはダングリングリファレンスが作成されるリスクが高まります。 _3 = 5;
_の場合と同様に、プログラマが意図していない冗長な操作を指定するリスクが高まります。
繰り返しますが、表現のカテゴリーは、プログラマーの表現力を支援するための設計上の決定であることを繰り返します。メモリや保存場所とは関係ありません。
最後に、抽象マシンとas-ifルールに。 C++は、一時オブジェクトがストレージとアドレスも持つ抽象マシンの観点から定義されています。一時オブジェクトのアドレスを出力する方法の例を先に示しました。
as-ifルールは、コンパイラーが生成する実際の実行可能ファイルの出力は、抽象マシンが生成する出力とのみ一致する必要があることを示しています。実行可能ファイルは、実際には抽象マシンと同じように機能する必要はなく、同じ結果を生成する必要があります。
したがって、_x = 5;
_のようなコードの場合、値_5
_の一時オブジェクトに抽象マシン内のメモリロケーションがあります。コンパイラは実際のマシンに物理ストレージを割り当てる必要はありません。 x
に_5
_が格納されていることを確認する必要があるだけで、追加のストレージの作成を必要としない、これを行う簡単な方法があります。
as-ifルールはプログラムのすべてに適用されますが、ここでの例では一時オブジェクトのみを参照しています。非一時的なオブジェクトも同様に最適化できます。 _int x; int y = 5; x = y; // other code that doesn't use y
_は_int x = 5;
_に変更できます。
プログラムの出力を変更する副作用のないクラス型にも同じことが当てはまります。例えば。左辺値x
が抽象マシンのストレージを持つオブジェクトを示していても、_std::string x = "foo"; std::cout << x;
_は_std::cout << "foo";
_に最適化できます。
lvalue
はlocator valueを表し、メモリ内の特定の場所を占めるオブジェクトを表します。
ロケーター値という用語も使用されます ここ :
C
Cプログラミング言語は、割り当ての役割が重要ではなくなったことを除いて、同様の分類法に従いました。C式は、「左辺値式」とその他(関数および非オブジェクト値)の間で分類されます。「左辺値」は、オブジェクト、「ロケーター値」[4]。
lvalue
ではないものはすべて、除外によりrvalue
です。すべての式はlavalue
またはrvalue
のいずれかです。
元々lvalue
という用語は、Cでは代入演算子の左側に留まる値を示すために使用されていました。ただし、const
キーワークにより、これは変更されました。すべてのlvalues
を割り当てることはできません。できるものはmodifiable lvalues
と呼ばれます。
また、リテラル変数と一時変数は左辺値ではありませんが、このステートメントには理由がありません。
この答え によれば、リテラルはlvalues
になる場合があります。
rvalue
です。これは、それらが既知のサイズであり、指定されたマシンコマンドに直接埋め込まれている可能性が高いためですハードウェアアーキテクチャ。 5
のメモリ位置は何ですか?lvalues
です。これは、それらが予測できないサイズを持ち、それ以外にそれらを表現する方法がないためです。メモリ内のオブジェクトとして。lvalue
はrvalue
に変換できます。たとえば、次の手順で
int a =5;
int b = 3;
int c = a+b;
演算子+
は2つのrvalues
を受け取ります。したがって、a
とb
は、合計される前にrvalues
に変換されます。変換の別の例:
int c = 6;
&c = 4; //ERROR: &c is an rvalue
逆にrvalue
をlvalue
に変換することはできません。
ただし、canはlvalue
から有効なrvalue
を生成できます。次に例を示します。
int arr[] = {1, 2};
int* p = &arr[0];
*(p + 1) = 10; // OK: p + 1 is an rvalue, but *(p + 1) is an lvalue
C++ 11では、右辺値参照は移動コンストラクターと移動割り当て演算子に関連しています。
詳細は この明確で十分に説明された投稿 にあります。