clangのC ++ 11ステータスページで「* thisの右辺値参照」と呼ばれる提案に出くわしました。
私は右辺値参照についてかなり読んで理解しましたが、私はこれについて知っているとは思いません。また、この用語を使用しても、ウェブ上で多くのリソースを見つけることができませんでした。
ページに提案書へのリンクがあります:N2439(移動のセマンティクスを* thisに拡張しています)が、そこから多くの例を取得していません。
この機能については何ですか?
clangのC ++ 11ステータスページで「* thisの右辺値参照」と呼ばれる提案に出くわしました。
私は右辺値参照についてかなり読んで理解しましたが、私はこれについて知っているとは思いません。また、この用語を使用しても、ウェブ上で多くのリソースを見つけることができませんでした。
ページに提案書へのリンクがあります:N2439(移動のセマンティクスを* thisに拡張しています)が、そこから多くの例を取得していません。
この機能については何ですか?
回答:
まず、「* 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-qualifiers(const
and 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
クラスのメンバー関数の型X
はX*
です。[...]
MyType(int a, double b) &&
か?
左辺値の参照修飾子フォームには追加の使用例があります。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&) &;
};
演算子は組み込み型の演算子のように機能し、左辺値のみを受け入れます。
クラスに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
std::move
第2版を必要とすると思いますか?また、なぜ右辺値参照が戻るのですか?
*this
が、混乱がどこから来ているのか理解できます。これは、ref-qualifierが、オーバーロードの解決と関数呼び出しの際に、「this」(ここでは意図的に引用されている)オブジェクトがバインドされている暗黙(または「非表示」)関数パラメーターのタイプを変更するためです。したがって、*this
Xeoが説明するように、これは修正されているので変更はありません。同じように、それはlvalue-作るか、または右辺値参照するには、「hiddden」パラメータの代わりに変更const
関数修飾子は、それが可能const
など。