C ++式のカテゴリ

左辺値右辺 などの式のカテゴリは、C ++言語の使用の実際的な側面よりも、C ++言語の基本的な理論的概念に関連しています。 このため、多くの経験豊富なプログラマでさえ、彼らが何を意味するかについて漠然とした考えを持っています。 この記事では、これらの用語の意味を可能な限り簡単に説明し、理論を実用的な例で希釈します。 すぐに予約します。この記事では、表現のカテゴリの最も完全で厳密な説明を提供するふりをしません。詳細については、ソースに直接お問い合わせください:C ++言語標準。


この記事にはかなり多くの英語の用語が含まれます。これは、ロシア語への翻訳が難しいものもあれば、異なるソースで異なる方法で翻訳されるものもあるためです。 したがって、英語の用語をイタリック体で強調し 、頻繁に示します

ちょっとした歴史


左辺値右辺 という用語は、Cに登場しました。値ではなく式を参照しているため、最初は用語に混乱が生じていたことに注意してください。 歴史的に、 左辺値は代入演算子の左にできるであり、 右辺値右にしかできないです。


lvalue = rvalue; 

しかし、そのような定義は本質をいくらか単純化し、歪めます。 C89標準は、 左辺値オブジェクトロケーターとして定義しました。 識別可能なメモリ位置を持つオブジェクト。 したがって、この定義に適合しないものはすべて右辺値カテゴリに含まれていました。


ビャルンは救助に急いで


C ++では、特に右辺値リンクと移動セマンティクスの概念を導入したC ++ 11標準の採用後に、式カテゴリの用語が非常に強力に進化しました。 新しい用語の出現の歴史は、興味深いことにStraustrupの記事「New」Value Terminologyで説明されています。


新しい、より厳密な用語は、2つのプロパティに基づいています。



アイデンティティを表現する式はglvalue一般化された値 )という用語で一般化され、ローミング式はrvalueと呼ばれます。 これら2つのプロパティの組み合わせにより、3つの主要な式のカテゴリが特定されました。


アイデンティティを持っているアイデンティティの欠如
移動できません左辺値-
移動できますxvalue価値

実際、C ++ 17標準では、 コピーの省略という概念が導入されています。コンパイラがオブジェクトのコピーと移動を回避できる状況を正式に規定しています。 この点で、 prvalueは必ずしも移動されるとは限りません。 詳細と例については、 こちらをご覧ください 。 ただし、これは式のカテゴリの一般的なスキームの理解には影響しません。


最新のC ++標準では、カテゴリの構造はそのようなスキームの形式で与えられます。


画像


一般的な用語で、カテゴリのプロパティ、および各カテゴリに含まれる言語表現を調べてみましょう。 各カテゴリの以下の式のリストは完全であるとは見なせないことに注意してください。より正確で詳細な情報については、C ++標準を直接参照してください。


glvalue


glvalueカテゴリの式には、次のプロパティがあります。



右辺値


右辺値カテゴリの式には、次のプロパティがあります。



 class A { public: A() = default; A(const A&) { std::cout << "A::A(const A&)\n"; } A(A&&) { std::cout << "A::A(A&&)\n"; } }; ......... A a; A b(a); //  A(const A&) A c(std::move(a)); //  A(A&&) 

技術的には、A &&は右辺値であり、定数左辺値参照と右辺 参照の両方を初期化するために使用できます。 しかし、このプロパティのおかげで、あいまいさはなく、 右辺値参照を受け入れるコンストラクタオプションが受け入れられます。

左辺値


プロパティ:



次の式は左辺値カテゴリに属します。



 void func() {} ......... auto* func_ptr = &func; // :     auto& func_ref = func; // :     int&& rrn = int(123); auto* pn = &rrn; // :    auto& rn = rrn; // :  lvalue- 


文字列リテラルは、C ++の他のすべてのリテラルとは、それが左辺値 (不変ではありますが)であるという点で正確に異なります。 たとえば、そのアドレスを取得できます。

 auto* p = &”Hello, world!”; //   ,    

価値


プロパティ:



次の式はprvalueカテゴリに属します。



xvalue


プロパティ:



xvalueカテゴリの式の例:



実際、 std :: move()を呼び出した結果、メモリ内のアドレスを取得したり、そのアドレスへのリンクを初期化することはできませんが、同時に、この式は多態的です:

 struct XA { virtual void f() { std::cout << "XA::f()\n"; } }; struct XB : public XA { virtual void f() { std::cout << "XB::f()\n"; } }; XA&& xa = XB(); auto* p = &std::move(xa); //  auto& r = std::move(xa); //  std::move(xa).f(); //  “XB::f()” 


特別な場合


コンマ演算子


組み込みのコンマ演算子の場合、式カテゴリは常に第2オペランドの式カテゴリと一致します。


 int n = 0; auto* pn = &(1, n); // lvalue auto& rn = (1, n); // lvalue 1, n = 2; // lvalue auto* pt = &(1, int(123)); // , rvalue auto& rt = (1, int(123)); // , rvalue 

ボイド式


voidを返す関数の呼び出し、 voidへの型変換式、および例外のスローはprvalueカテゴリの式と見なされますが、参照の初期化や関数の引数として使用することはできません。


三項比較演算子


式のカテゴリの定義a ? b : c a ? b : cケースは自明ではなく、すべて2番目と3番目の引数( bおよびc )のカテゴリに依存します。



三項演算子については、引数bおよびcに暗黙的な変換を適用できるいくつかの規則が定義されていますが、これは記事範囲をやや超えています。興味がある場合は、標準の条件演算子[expr.cond]セクションを参照することをお勧めします。


 int n = 1; int v = (1 > 2) ? throw 1 : n; // lvalue, .. throw   void,    n ((1 < 2) ? n : v) = 2; //  lvalue,  ,   ((1 < 2) ? n : int(123)) = 2; //   , ..    prvalue 

クラスと構造のフィールドとメソッドへの参照


amおよびp->mという形式の式(ここでは組み込み演算子->について説明しています)には、次の規則が適用されます。



クラスメンバーへのポインター( a.*mpおよびp->*mp )の場合、ルールは同様です。



ビットフィールド


ビットフィールドは低レベルのプログラミングに便利なツールですが、それらの実装は式カテゴリの一般的な構造から多少外れています。 たとえば、ビットフィールドへの呼び出しは、割り当て演算子の左側に存在する可能性があるため左辺値と思われます。 同時に、ビットフィールドのアドレスを取得したり、それらによって非定数リンクを初期化することはできません。 ビットフィールドへの定数参照を初期化できますが、オブジェクトの一時コピーが作成されます。


ビットフィールド[class.bit]
const T&型の参照の初期化子がビットフィールドを参照する左辺値である場合、参照はビットフィールドの値を保持するために一時的に初期化されます。 参照はビットフィールドに直接バインドされていません。

 struct BF { int f:3; }; BF b; bf = 1; // OK auto* pb = &b.f; //  auto& rb = bf; //  

結論の代わりに


冒頭で述べたように、上記の説明は完全であると主張するのではなく、表現のカテゴリの一般的なアイデアを提供するだけです。 このビューは、標準のセクションとコンパイラエラーメッセージの理解をわずかに向上させます。



Source: https://habr.com/ru/post/J441742/


All Articles