1. ホーム
  2. c++

[解決済み】「*this の rvalue 参照」とは何ですか?

2022-04-04 19:46:33

質問

clangの「*this"rvalue reference」という提案に出会いました。 C++11ステータスページ .

rvalue参照についてはかなり読み込んで理解しているのですが、これについては知らないようです。また、Web上でこの用語を使ったリソースをあまり見つけることができませんでした。

ページ内に提案論文へのリンクがあります。 N2439 (Extending move semantics to *this), but I also not getting much examples from there.

この機能はどのようなものですか?

解決方法は?

まず、quot;ref-qualifiers for *this" は、単なるマーケティング・ステートメントに過ぎないのです。のタイプは *this は決して変わりません。この記事の一番下をご覧ください。でも、この表現の方がずっとわかりやすいですね。

次に、以下のコードでは、呼び出す関数を リファレンシャルクォリファイア の暗黙のオブジェクト・パラメータを使用します。 :

// 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

全体として、関数が呼び出されたオブジェクトがrvalue(無名テンポラリなど)である場合に、その事実を利用できるようにするために行われます。さらに例として次のコードを見てみましょう。

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 ( constvolatile ) と リファレンシャルクォリフィエ ( &&& ).


注:多くの標準的な引用と過負荷解消の説明はこの後にあります

この仕組みと、@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

確かに以下のようにfree関数を呼び出してほしいですよね。

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

そのため、メンバー関数と非メンバー関数は、いわゆるオーバーロードセットに含まれます。解決を複雑にしないために、標準的な引用の太字の部分が存在する。さらに、これは我々にとって重要なビットである(同条項)。

p4 非静的メンバ関数の場合、暗黙のオブジェクトパラメータの型は

  • 「へのlvalue参照 cv X " なしで宣言された関数に対して リフクォリファイア または & リフクォリファイア

  • 「へのrvalue参照 cv X " で宣言された関数に対して && リフクォリファイア

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

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

しかし、もし リファレンシャルクォリファイア (そのため、この関数をオーバーロードしていない)、その f1 となる はrvalueにマッチします(やはり §13.3.1 ):

p5 [...] 静的でないメンバ関数が リファレンシャルクォリファイア 追加ルールが適用されます。

  • 暗黙のオブジェクトパラメータが const -qualifiedでは、他のすべての点で引数が暗黙のオブジェクトパラメータの型に変換できる限り、パラメータにrvalueをバインドすることができます。
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 常に l値である。

§5.3.1 [expr.unary.op] p1

単項演算子 * 演算子は インダイレクト : 適用される式は,オブジェクト型へのポインタ,または関数型へのポインタでなければならない。 であり、結果は lvalue である。 式が指し示すオブジェクトまたは関数を参照する。

§9.3.2 [class.this] p1

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