1547

C++03 では、式は右辺値または左辺値のいずれかです。

C++11 では、式は次のようになります。

  1. 右辺値
  2. 左辺値
  3. x値
  4. glvalue
  5. 価格

2カテゴリーが5カテゴリーになりました。

  • これらの表現の新しいカテゴリは何ですか?
  • これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?
  • C++0x の右辺値と左辺値のカテゴリは C++03 と同じですか?
  • なぜこれらの新しいカテゴリが必要なのですか? WG21の神々は、ただの人間である私たちを混乱させようとしているだけですか?
4

13 に答える 13

705

このドキュメントはそれほど短い紹介ではないかもしれません:n3055

虐殺全体は、移動のセマンティクスから始まりました。移動できる表現とコピーできない表現ができたら、突然ルールを把握しやすくなり、移動できる表現とその方向を区別する必要がありました。

ドラフトに基づいて私が推測することから、r / l値の区別は同じままですが、物事を動かすという状況でのみ混乱します。

それらは必要ですか?新機能を失いたいのであれば、おそらくそうではありません。しかし、より良い最適化を可能にするために、おそらくそれらを採用する必要があります。

n3055の引用:

  • 左辺値(いわゆる、歴史的には、左辺値が代入式の左側に表示される可能性があるため)は、関数またはオブジェクトを指定します。 [例:Eがポインタ型の式である場合、は*E が指すオブジェクトまたは関数を参照する左辺値式E です。別の例として、戻り型が左辺値参照である関数を呼び出した結果は左辺値です。]
  • xvalue (「 eXpiring」値)も、通常はその存続期間の終わり近くにあるオブジェクトを参照します(たとえば、そのリソースを移動できるようにするため)。xvalueは、右辺値参照を含む特定の種類の式の結果です。 [例:戻り値が右辺値参照である関数を呼び出した結果はxvalueです。]
  • glvalue (「一般化された」左辺値)は、左辺値 またはx値です
  • 右辺値(いわゆる、歴史的には、右辺値は代入式の右側に表示される可能性があるため)は、x値、一時オブジェクトまたはそのサブオブジェクト、またはオブジェクトに関連付けられていない値です。
  • prvalue (「純粋な」右辺値)は、x値ではない右辺値です [例:戻り型が参照ではない関数を呼び出した結果はprvalueです]

問題のドキュメントは、新しい命名法の導入の結果として発生した標準の正確な変更を示しているため、この質問の優れたリファレンスです。

于 2010-08-30T15:09:16.103 に答える
363

これらの表現の新しいカテゴリは何ですか?

FCD (n3092)には優れた説明があります。

— 左辺値 (左辺値は代入式の左側に現れる可能性があるため、歴史的にそう呼ばれています) は、関数またはオブジェクトを指定します。[ 例: E がポインター型の式の場合、*E は、E が指すオブジェクトまたは関数を参照する左辺値式です。別の例として、戻り値の型が左辺値参照である関数を呼び出した結果は、左辺値です。—終わりの例]

— xvalue (「eXpiring」値) もオブジェクトを参照します。通常は、オブジェクトの寿命が終わりに近づいています (たとえば、そのリソースが移動される可能性があるため)。xvalue は、右辺値参照 (8.3.2) を含む特定の種類の式の結果です。[ 例: 戻り値の型が右辺値参照である関数を呼び出した結果は、xvalue です。—終わりの例]

— glvalue (「一般化された」左辺値) は、左辺値または xvalue です。

— 右辺値 (右辺値は代入式の右側に現れる可能性があるため、歴史的にそう呼ばれています) は、xvalue、一時オブジェクト (12.2) またはそのサブオブジェクト、またはオブジェクトに関連付けられていない値です。

— prvalue (「純粋な」右辺値) は、xvalue ではない右辺値です。【例:戻り値の型が参照でない関数を呼び出した結果がprvalueとなる。12、7.3e5、または true などのリテラルの値も prvalue です。—終わりの例]

すべての式は、この分類法の基本分類の 1 つ (左辺値、x 値、または前値) に正確に属します。式のこのプロパティは、その値カテゴリと呼ばれます。[ 注: 節 5 の各組み込み演算子の説明は、それが生成する値のカテゴリと、それが期待するオペランドの値のカテゴリを示しています。たとえば、組み込みの代入演算子は、左側のオペランドが左辺値であり、右側のオペランドが prvalue であると想定し、結果として左辺値を生成します。ユーザー定義演算子は関数であり、期待値と生成値のカテゴリは、パラメーターと戻り値の型によって決まります。—終わりのメモ

ただし、セクション3.10 左辺値と右辺値全体を読むことをお勧めします。

これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?

また:

分類法

C++0x の右辺値と左辺値のカテゴリは C++03 と同じですか?

右辺値のセマンティクスは、特に移動セマンティクスの導入によって進化しました。

なぜこれらの新しいカテゴリが必要なのですか?

そのため、移動の構築/割り当てを定義してサポートできます。

于 2010-08-30T15:18:36.363 に答える
203

最後の質問から始めます。

なぜこれらの新しいカテゴリが必要なのですか?

C++ 標準には、式の値カテゴリを扱う多くの規則が含まれています。左辺値と右辺値を区別するルールもあります。たとえば、オーバーロードの解決に関しては。他のルールでは、glvalue と prvalue が区別されます。たとえば、不完全型または抽象型の glvalue を持つことはできますが、不完全型または抽象型の prvalue はありません。この用語を使用する前は、glvalue/prvalue を実際に区別する必要があるルールは、左辺値/右辺値を参照していましたが、意図せずに間違っていたか、ルールの説明と例外がたくさん含まれていました。右辺値参照...」. したがって、glvalues と prvalues の概念に独自の名前を付けるのは良い考えのようです。

これらの表現の新しいカテゴリは何ですか? これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?

C++98 と互換性のある左辺値と右辺値という用語がまだあります。右辺値を xvalues と prvalues の 2 つのサブグループに分割しただけで、lvalues と xvalues を glvalues と呼びます。Xvalues は、名前のない右辺値参照の新しい種類の値カテゴリです。すべての式は、lvalue、xvalue、prvalue の 3 つのいずれかです。ベン図は次のようになります。

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

関数の例:

int   prvalue();
int&  lvalue();
int&& xvalue();

ただし、名前付き右辺値参照は左辺値であることも忘れないでください。

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
于 2010-08-31T08:08:28.323 に答える
190

なぜこれらの新しいカテゴリが必要なのですか? WG21 の神々は、ただの人間である私たちを混乱させようとしているだけですか?

他の回答(多くは良いですが)がこの特定の質問に対する回答を本当に捉えているとは思いません。はい、これらのカテゴリなどは移動セマンティクスを可能にするために存在しますが、複雑さが存在する理由は 1 つあります。これは、C++11 でのものを移動する際の 1 つの絶対的な規則です。

移動しても間違いなく安全である場合にのみ移動する必要があります。

それが、これらのカテゴリが存在する理由です。それらから移動しても安全な値について話し、そうでない場合に値について話すことができるようにするためです。

右辺値参照の初期のバージョンでは、動きは簡単に起こりました。簡単すぎる。ユーザーが本当に意図していないときに暗黙のうちに物を動かす可能性がたくさんあることは簡単に言えます。

何かを移動しても安全な状況は次のとおりです。

  1. 一時オブジェクトまたはそのサブオブジェクトの場合。(前値)
  2. ユーザーが明示的に移動するように指示した場合

これを行う場合:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

これは何をしますか?仕様の古いバージョンでは、5 つの値が導入される前に、これが動きを引き起こしました。もちろんそうです。右辺値参照をコンストラクターに渡したので、右辺値参照を取るコンストラクターにバインドされます。それは明らかです。

これには 1 つだけ問題があります。あなたはそれを動かすように頼んだわけではありません。ああ、それが手がかりになるべきだったと言うかもしれませんが、&&それはそれが規則を破ったという事実を変えません. val一時的なものには名前がないため、一時的なものではありません。temporary の有効期間を延長した可能性がありますが、それは一時的ではないことを意味します。他のスタック変数と同じです。

一時的なものではなく、移動するように依頼していない場合、移動は間違っています。

明らかな解決策はval、左辺値を作成することです。これは、そこから移動できないことを意味します。いいよ; 名前が付いているので、左辺値です。

それができたら、SomeType&&どこでも同じことを意味するとは言えなくなります。これで、名前付き右辺値参照と名前なし右辺値参照を区別できました。名前付き右辺値参照は左辺値です。それが上記の解決策でした。では、名前のない右辺値参照 (上記の戻り値) を何と呼びますFuncか?

左辺値から移動できないため、左辺値ではありません。そして、 ;を返すことで移動できる必要があります。&&他にどうやって何かを動かすと明示的に言うことができますか? std::move結局のところ、それが返ってきます。式の左側にある可能性があるため、右辺値 (古いスタイル) ではありません (実際にはもう少し複雑です。この質問と以下のコメントを参照してください)。左辺値でも右辺値でもありません。それは新しい種類のものです。

私たちが持っているのは、暗黙的に移動可能であることを除いて、左辺値として扱うことができる値です。これを xvalue と呼びます。

xvalues は、値の他の 2 つのカテゴリを取得するものであることに注意してください。

  • prvalue は、以前のタイプの右辺値の新しい名前にすぎません。つまり、これらは xvalue ではない右辺値です。

  • Glvalues は、多くのプロパティを共有しているため、1 つのグループ内の xvalues と lvalues の和集合です。

つまり、すべては xvalues と、移動を正確に特定の場所のみに制限する必要性に帰着します。これらの場所は右辺値カテゴリによって定義されます。prvalues は暗黙的な移動であり、xvalues は明示的な移動です ( std::movexvalue を返します)。

于 2012-03-04T06:32:42.690 に答える
81

前書き

ISOC++11 (正式には ISO/IEC 14882:2011) は、C++ プログラミング言語の標準の最新バージョンです。これには、いくつかの新しい機能と概念が含まれています。たとえば、次のとおりです。

  • 右辺値参照
  • xvalue、glvalue、prvalue 式の値のカテゴリ
  • セマンティクスを移動する

新しい式の値カテゴリの概念を理解したい場合は、右辺値と左辺値の参照があることに注意する必要があります。右辺値を非 const 右辺値参照に渡すことができることを知っておくことをお勧めします。

int& r_i=7; // compile error
int&& rr_i=7; // OK

作業草案 N3337 (発行された ISOC++11 標準に最も類似した草案) から Lvalues と rvalues というタイトルのサブセクションを引用すると、値カテゴリの概念のいくつかの直感を得ることができます。

3.10 左辺値と右辺値 [basic.lval]

1 式は、図 1 の分類に従って分類されます。

  • 左辺値 (左辺値は代入式の左側に現れる可能性があるため、歴史的にそう呼ばれています) は、関数またはオブジェクトを指定します。[ 例: E がポインター型の式の場合、*E は、E が指すオブジェクトまたは関数を参照する左辺値式です。別の例として、戻り値の型が左辺値参照である関数を呼び出した結果は、左辺値です。—終わりの例]
  • xvalue (「eXpiring」値) もオブジェクトを参照します。通常は、オブジェクトの有効期間の終わりが近づいています (たとえば、そのリソースが移動される可能性があるため)。xvalue は、右辺値参照 (8.3.2) を含む特定の種類の式の結果です。[ 例: 戻り値の型が右辺値参照である関数を呼び出した結果は、xvalue です。—終わりの例]
  • glvalue (「一般化された」左辺値) は、左辺値または xvalue です。
  • 右辺値 (右辺値は代入式の右側に現れる可能性があるため、歴史的にそう呼ばれています) は、xvalue、一時オブジェクト (12.2) またはそのサブオブジェクト、またはオブジェクトに 関連付けられて
    いない値です。
  • prvalue (「純粋な」右辺値) は、xvalue ではない右辺値です。【例:戻り値の型が参照でない関数の呼び出し結果は
    prvalue。12、7.3e5、または
    true などのリテラルの値も prvalue です。—終わりの例]

すべての式は、この分類法の基本分類の 1 つ (左辺値、x 値、または前値) に正確に属します。式のこのプロパティは、その値カテゴリと呼ばれます。

しかし、このサブセクションが概念を明確に理解するのに十分であるかどうかはよくわかりません。「通常」は実際には一般的ではなく、「寿命の終わり近く」は実際には具体的ではなく、「右辺値参照を含む」は実際には明確ではないためです。 「例: 戻り値の型が右辺値参照である関数を呼び出した結果は xvalue です。」ヘビが尻尾を噛んでいるような音。

プライマリ バリュー カテゴリ

すべての式は、厳密に 1 つのプライマリ値カテゴリに属します。これらの値カテゴリは、lvalue、xvalue、および prvalue カテゴリです。

左辺値

式 E は、E の外部からアクセスできるようにする ID (アドレス、名前、またはエイリアス) を既に持っているエンティティを E が参照する場合にのみ、左辺値カテゴリに属します。

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

x値

式 E は、次の場合にのみ xvalue カテゴリに属します。

— 暗黙的または明示的に、戻り値の型が返されるオブジェクトの型への右辺値参照である関数を呼び出した結果、または

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

— オブジェクト型への右辺値参照へのキャスト、または

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

— オブジェクト式が xvalue である非参照型の非静的データ メンバーを指定するクラス メンバー アクセス式、または

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

— 最初のオペランドが xvalue で、2 番目のオペランドがデータ メンバーへのポインターであるメンバーへのポインター式。

上記のルールの効果は、オブジェクトへの名前付き右辺値参照が左辺値として扱われ、オブジェクトへの名前なし右辺値参照が xvalue として扱われることに注意してください。関数への右辺値参照は、名前が付けられているかどうかにかかわらず、左辺値として扱われます。

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

式 E が prvalue カテゴリに属する​​のは、E が lvalue カテゴリにも xvalue カテゴリにも属していない場合のみです。

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

混合価値カテゴリー

さらに 2 つの重要な混合値カテゴリがあります。これらの値カテゴリは、rvalue カテゴリと glvalue カテゴリです。

右辺値

式 E が右辺値カテゴリに属する​​のは、E が xvalue カテゴリまたは prvalue カテゴリに属している場合のみです。

この定義は、式 E が右辺値カテゴリに属する​​ことを意味することに注意してください。これは、E が、E YET の外部でアクセス可能にするアイデンティティを持たないエンティティを参照する場合に限られます。

glvalues

E が lvalue カテゴリまたは xvalue カテゴリに属する​​場合に限り、式 E は glvalue カテゴリに属します。

実用的な規則

Scott Meyer は、右辺値と左辺値を区別するための非常に役立つ経験則を公開しています。

  • 式のアドレスを取得できる場合、その式は左辺値です。
  • 式の型が左辺値参照 (T& や const T& など) の場合、その式は左辺値です。
  • それ以外の場合、式は右辺値です。概念的に (通常は実際にも)、右辺値は、関数から返されたオブジェクトや暗黙的な型変換によって作成されたオブジェクトなどの一時オブジェクトに対応します。ほとんどのリテラル値 (10 や 5.3 など) も右辺値です。
于 2016-01-20T13:46:20.917 に答える
44

cppreference.com の値カテゴリの説明に出くわすまで、私はこれに長い間苦労してきました。

実にシンプルなのですが、暗記するのが難しい方法で説明されていることがよくあります。ここでは非常に概略的に説明されています。ページの一部を引用します。

プライマリ カテゴリ

主要な値のカテゴリは、式の 2 つのプロパティに対応します。

  • has identity : 式が別の式と同じエンティティを参照しているかどうかを判断できます。たとえば、オブジェクトのアドレスまたはそれらが識別する関数 (直接的または間接的に取得) を比較することによって可能です。

  • から移動できます: 移動コンストラクター、移動代入演算子、または移動セマンティクスを実装する別の関数オーバーロードが式にバインドできます。

次のような表現:

  • ID を持ち、移動できないものは、左辺値式と呼ばれます。
  • ID を持ち、移動できるものはxvalue 式と呼ばれます。
  • ID を持たず、移動できるものはprvalue 式と呼ばれます。
  • ID がなく、移動できないものは使用されません。

左辺値

左辺値 (「左の値」) 式は、同一性を持ちから移動できない式です。

rvalue (C++11未満)、prvalue (C++11以上)

prvalue (「純粋な右辺値」) 式は、同一性を持た、 から移動できる式です。

x値

xvalue (「期限切れの値」) 式は、ID を持ちから移動できる式です。

glvalue

glvalue (「一般化された左辺値」) 式は、左辺値または xvalue のいずれかである式です。アイデンティティーを持っています。移動する場合と移動しない場合があります。

右辺値 (C++11以上)

右辺値 (「正しい値」) 式は、prvalue または xvalue のいずれかである式です。から移動できます。ID がある場合とない場合があります。

それでは、それを表に入れましょう:

(=右辺値) から移動可能 から移動できません
ID を持っている (= glvalue) x値 左辺値
アイデンティティーなし 価格 使用されていない
于 2016-06-17T02:20:32.997 に答える
36

C++03 のカテゴリはあまりにも制限されているため、式の属性への右辺値参照の導入を正しく捉えることができません。

それらの導入により、名前のない右辺値参照が右辺値に評価されるため、オーバーロード解決が右辺値参照バインディングを優先し、コピー コンストラクターよりもムーブ コンストラクターを選択するようになると言われていました。しかし、これにより、たとえば動的型や修飾子など、あらゆるところで問題が発生することが判明しました。

これを示すために、考えてみましょう

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

xvalue 以前のドラフトでは、これは許可されていました。C++03 では、非クラス型の右辺値が cv 修飾されないためです。しかし、ここではオブジェクト (= メモリ!) を参照するため、右辺値参照の場合に適用されることを意図しておりconst非クラスの右辺値から const を削除するのは、主にオブジェクトが存在しないためです。

動的タイプの問題は、同様の性質のものです。C++03 では、クラス型の右辺値には既知の動的型があります。これは、その式の静的型です。それを別の方法で行うには、左辺値に評価される参照または逆参照が必要だからです。これは、名前のない右辺値参照には当てはまりませんが、多態的な動作を示す可能性があります。そこで、それを解決するために、

  • 名前のない右辺値参照はxvaluesになります。それらは修飾することができ、動的タイプが異なる可能性があります。意図したように、オーバーロード中に右辺値参照を優先し、非 const 左辺値参照にバインドしません。

  • 以前は右辺値 (リテラル、非参照型へのキャストによって作成されたオブジェクト) であったものは、今ではprvalueになります。これらは、オーバーロード中に xvalues と同じ優先度を持ちます。

  • 以前は左辺値であったものは、左辺値のままです。

また、2 つのグループ化が行われ、修飾可能で異なる動的型を持つことができるもの ( glvalues ) と、オーバーロードが右辺値参照バインディングを優先するもの( rvalues ) をキャプチャします。

于 2010-08-30T16:46:28.920 に答える
20

これらの新しいカテゴリは、既存の右辺値および左辺値カテゴリとどのように関連していますか?

C++03 の左辺値は引き続き C++11 の左辺値ですが、C++03 の右辺値は C++11 では prvalue と呼ばれます。

于 2010-08-30T15:45:59.547 に答える
14

上記の優れた回答への1つの補遺.Stroustrupを読んだ後でも私を混乱させ、右辺値/左辺値の違いを理解していると思った. あなたが見るとき

int&& a = 3

を型として読んで、それが右辺値int&&であると結論付けたくなります。aそうではありません:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a名前があり、事実上左辺値です。を;&&の型の一部と考えないでください。これは、何にバインドできるaかを示すものにすぎません。a

T&&これは、コンストラクターの型引数の場合に特に重要です。あなたが書くなら

Foo::Foo(T&& _t) : t{_t} {}

にコピー_ttます。あなたが必要

Foo::Foo(T&& _t) : t{std::move(_t)} {}移動したい場合。!を省略したときにコンパイラが警告してくれましたかmove?

于 2016-07-25T04:26:13.860 に答える