「* thisの右辺値参照」とは何ですか?


238

clangのC ++ 11ステータスページで「* thisの右辺値参照」と呼ばれる提案に出くわしました。

私は右辺値参照についてかなり読んで理解しましたが、私はこれについて知っているとは思いません。また、この用語を使用しても、ウェブ上で多くのリソースを見つけることができませんでした。

ページに提案書へのリンクがあります:N2439(移動のセマンティクスを* thisに拡張しています)が、そこから多くの例を取得していません。

この機能については何ですか?

回答:


293

まず、「* thisのref-qualifiers」は単なる「マーケティングステートメント」です。のタイプは*this変更されません。この投稿の下部を参照してください。ただし、この表現を使用すると理解しやすくなります。

次に、次のコードは、関数の「暗黙のオブジェクトパラメータ」のref-qualifierに基づいて、呼び出される関数を選択します

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

出力:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

すべては、関数が呼び出されるオブジェクトが右辺値(名前のない一時的なものなど)であるという事実を利用できるようにするために行われます。さらなる例として次のコードを見てください:

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

これは少し工夫されているかもしれませんが、あなたはアイデアを得るべきです。

cv-qualifiersconstand volatile)とref-qualifiers&and &&)を組み合わせることができることに注意してください。


注:多くの標準的な引用とオーバーロードの解決の説明はここから!

†これがどのように機能するか、そして@Nicol Bolasの答えが少なくとも部分的に間違っている理由を理解するために、C ++標準を少し掘り下げる必要があります(@Nicolの答えが間違っている理由を説明している部分は、それだけに興味がある)。

呼び出される関数は、オーバーロード解決と呼ばれるプロセスによって決定されます。このプロセスはかなり複雑であるため、ここでは重要な部分だけに触れます。

まず、メンバー関数のオーバーロード解決がどのように機能するかを確認することが重要です。

§13.3.1 [over.match.funcs]

p2候補関数のセットには、同じ引数リストに対して解決されるメンバー関数と非メンバー関数の両方を含めることができます。引数とパラメーターのリストがこの異種セット内で比較できるように、メンバー関数は、メンバー関数が呼び出されたオブジェクトを表す、暗黙のオブジェクトパラメーターと呼ばれる追加のパラメーターを持つと見なされます。[...]

p3同様に、適切な場合、コンテキストは、操作されるオブジェクトを示す暗黙のオブジェクト引数を含む引数リストを作成できます。

メンバー関数と非メンバー関数を比較する必要があるのはなぜですか?演算子の過負荷、それが理由です。このことを考慮:

struct foo{
  foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

あなたは確かに以下が無料の関数を呼び出すことを望んでいるでしょうね?

char const* s = "free foo!\n";
foo f;
f << s;

これが、メンバー関数と非メンバー関数がいわゆるオーバーロードセットに含まれている理由です。解決を簡単にするために、標準的な見積もりの​​太字部分が存在します。さらに、これは私たちにとって重要なビットです(同じ節):

p4非静的メンバー関数の場合、暗黙的なオブジェクトパラメーターの型は次のとおりです。

  • 「への左辺値参照CV Xせずに宣言された関数のための」REF-修飾子またはで& REF-修飾子

  • 「への右辺値参照品種 Xで宣言された機能のための」&& REF-修飾子

ここXで、は関数がメンバーであるクラスであり、cvはメンバー関数宣言のcv-qualificationです。[...]

p5オーバーロードの解決中、対応する引数の変換は次の追加ルールに従うため、暗黙のオブジェクトパラメータ[...]はそのアイデンティティを保持します。

  • 暗黙的なオブジェクトパラメータの引数を保持する一時オブジェクトを導入することはできません。そして

  • 型の一致を実現するためにユーザー定義の変換を適用することはできません

[...]

(最後のビットは、メンバー関数(または演算子)が呼び出されるオブジェクトの暗黙的な変換に基づいてオーバーロード解決をだますことができないことを意味します。)

この投稿の上部にある最初の例を見てみましょう。前述の変換後、オーバーロードセットは次のようになります。

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

次に、暗黙のオブジェクト引数を含む引数リストが、オーバーロードセットに含まれるすべての関数のパラメーターリストと照合されます。この場合、引数リストにはそのオブジェクト引数のみが含まれます。それがどのように見えるか見てみましょう:

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
       // kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
       // taken out of overload-set

セット内のすべてのオーバーロードがテストされた後、残りが1つだけの場合、オーバーロードの解決は成功し、その変換されたオーバーロードにリンクされた関数が呼び出されます。同じことは、2回目の 'f'の呼び出しにも当てはまります。

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
            // taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
            // kept in overload-set

注しかし、我々はすべての提供していなかった、ということREF-修飾子を(そのように機能をオーバーロードされていない)、それはf1 でしょう(まだ右辺値と一致§13.3.1):

p5 [...] ref-qualifierなしで宣言された非静的メンバー関数の場合、追加の規則が適用されます。

  • 暗黙的なオブジェクトパラメータがconst修飾されていなくても、他のすべての点で引数が暗黙的なオブジェクトパラメータのタイプに変換できる限り、右辺値をパラメータにバインドできます。
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

では、@ Nicolの答えが少なくとも部分的に間違っている理由について。彼は言う:

この宣言はのタイプを変更することに注意してください*this

それは、間違って*thisいる、常に左辺値:

§5.3.1 [expr.unary.op] p1

単項演算*子は間接参照を実行します。適用される式は、オブジェクト型へのポインターまたは関数型へのポインターであり、結果は、指すオブジェクトまたは関数を参照する左辺値です。

§9.3.2 [class.this] p1

非静的(9.3)メンバー関数の本体では、キーワードthisはprvalue式であり、その値は関数が呼び出されるオブジェクトのアドレスです。thisクラスのメンバー関数の型XX*です。[...]


「変換後」セクションの直後のパラネット型は「テスト」ではなく「foo」である必要があると思います。
ライアー

@ryaner:良い発見、ありがとう。パラメータではなく関数のクラス識別子が間違っています。:)
Xeo

私はその部分を読んで、fはfooの中に含まれていると思ったときに申し訳ありませんおっと、私はおもちゃのクラスという名前のテストを忘れてしまったので、私のコメント...
ryaner

これはコンストラクタで実行できますMyType(int a, double b) &&か?
ヘルマン・Diago

2
「* thisのタイプは決して変更されない」r / l値の修飾に基づいて変更されないことを少し明確にする必要があります。しかし、const / non-constの間で変化する可能性があります。
xaxxon 2016年

78

左辺値の参照修飾子フォームには追加の使用例があります。C ++ 98には、const右辺値であるクラスインスタンスに対して非メンバー関数を呼び出すことができる言語があります。これは、右辺値の概念そのものに反し、組み込み型の機能から逸脱する、あらゆる種類の奇妙さをもたらします。

struct S {
  S& operator ++(); 
  S* operator &(); 
};
S() = S();      // rvalue as a left-hand-side of assignment!
S& foo = ++S(); // oops, dangling reference
&S();           // taking address of rvalue...

左辺値参照修飾子はこれらの問題を解決します:

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};

演算子は組み込み型の演算子のように機能し、左辺値のみを受け入れます。


28

クラスに2つの関数があり、両方とも同じ名前とシグネチャがあるとします。しかし、そのうちの1つが宣言されていconstます。

void SomeFunc() const;
void SomeFunc();

クラスインスタンスがでない場合const、オーバーロード解決は非constバージョンを優先的に選択します。インスタンスがのconst場合、ユーザーはconstバージョンのみを呼び出すことができます。また、thisポインタはconstポインタなので、インスタンスを変更することはできません。

「これのr値参照」が行うことは、別の選択肢を追加できるようにすることです。

void RValueFunc() &&;

これにより、ユーザーが適切なr値を介して呼び出す場合にのみ呼び出すことができる関数を作成できます。したがって、これがタイプの場合Object

Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.

このように、オブジェクトがr値を介してアクセスされているかどうかに基づいて、動作を特殊化できます。

r値の参照バージョンと非参照バージョンの間でオーバーロードすることは許可されていないことに注意してください。つまり、メンバー関数名がある場合、そのすべてのバージョンでl / r値修飾子を使用するか、いずれも使用thisしません。あなたはこれを行うことができません:

void SomeFunc();
void SomeFunc() &&;

これを行う必要があります:

void SomeFunc() &;
void SomeFunc() &&;

この宣言はのタイプを変更することに注意してください*this。これは、&&すべてのバージョンがr値の参照としてメンバーにアクセスすることを意味します。したがって、オブジェクト内から簡単に移動することが可能になります。提案の最初のバージョンで示されている例は次のとおりです(注:C ++ 11の最終バージョンでは以下は正しくない可能性があります。これは、最初の「このからのr値」提案から直接のものです):

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

2
私はあなたがstd::move第2版を必要とすると思いますか?また、なぜ右辺値参照が戻るのですか?
Xeo

1
@Xeo:それが提案の例だったからです。それが現在のバージョンでも動作するかどうかはわかりません。そして、r値の参照が返される理由は、動きはそれをキャプチャする人次第であるべきだからです。彼が実際に値の代わりに&&にそれを格納したい場合に備えて、それはまだ起こりません。
Nicol Bolas

3
そうですね、2つ目の質問の理由をちょっと考えました。でも、一時的なメンバーへの右辺値の参照は、一時的なメンバーまたはそのメンバーの寿命を延ばすのでしょうか。少し前にSOでそれについての質問を見たと誓うことができます...
Xeo

1
@Xeo:それは完全に真実ではありません。オーバーロードの解決では、非constバージョンが存在する場合、常にそれが選択されます。constバージョンを取得するには、キャストを行う必要があります。明確にするために投稿を更新しました。
Nicol Bolas、2011

15
私が説明するかもしれないと思ったのは、結局私がこの機能をC ++ 11用に作成したことです;)Xeoは、それがのタイプを変更しないと主張するのは正しいです*thisが、混乱がどこから来ているのか理解できます。これは、ref-qualifierが、オーバーロードの解決と関数呼び出しの際に、「this」(ここでは意図的に引用されている)オブジェクトがバインドされている暗黙(または「非表示」)関数パラメーターのタイプを変更するためです。したがって、*thisXeoが説明するように、これは修正されているので変更はありません。同じように、それはlvalue-作るか、または右辺値参照するには、「hiddden」パラメータの代わりに変更const関数修飾子は、それが可能constなど。
bronekk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.