web-dev-qa-db-ja.com

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

Clangの C++ 11ステータスページ にある「* thisの右辺値参照」という提案に出くわしました。

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

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

この機能は何ですか?

229
ryaner

まず、「* thisの参照修飾子」は単なる「マーケティングステートメント」です。 _*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-qualifiers const and volatile)と ref-qualifiers を組み合わせることができることに注意してください。 (_&_および_&&_)。


注:多くの標準引用符と、これ以降のオーバーロード解決の説明!

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

どの関数が呼び出されるかは、 overload resolution と呼ばれるプロセスによって決定されます。このプロセスはかなり複雑であるため、重要な部分のみを説明します。

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

_§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非静的メンバー関数の場合、暗黙的なオブジェクトパラメーターの型は

  • ref-qualifier なしで、または_&_を使用して宣言された関数の「 cv X」への左辺値参照」 ref-qualifier

  • _&&_で宣言された関数の「 cv Xへの右辺値参照」 ref-qualifier

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

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

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

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

[...]

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

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

_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つだけが残っている場合、オーバーロード解決は成功し、その変換されたオーバーロードにリンクされた関数が呼び出されます。同じことは、「f」の2回目の呼び出しにも当てはまります。

_// 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-qualifier を提供していなかった場合(関数をオーバーロードしていない場合)、_f1_ would 右辺値に一致(まだ_§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_は always の左辺値です:

_§5.3.1 [expr.unary.op] p1_

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

_§9.3.2 [class.this] p1_

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

284
Xeo

左辺値参照修飾子形式には追加の使用例があります。 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&) &;
};

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

77
JohannesD

クラスに2つの関数があり、どちらも同じ名前と署名を持っているとします。しかし、そのうちの1つはconstと宣言されています:

void SomeFunc() const;
void SomeFunc();

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

「これに対するr値参照」は、別の代替手段を追加できるようにします。

void RValueFunc() &&;

これにより、ユーザーが適切なr値を介して呼び出した場合にonlyを呼び出すことができる関数を使用できます。したがって、これが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値参照バージョンと非参照バージョンの間でオーバーロードすることは許可されていないことに注意してください。つまり、メンバー関数名がある場合、そのすべてのバージョンはthisでl/r-value修飾子を使用するか、使用しません。これはできません:

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

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

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

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

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
28
Nicol Bolas