clang のC++11 ステータス ページで、「*this の右辺値参照」という提案に出会いました。
私は右辺値参照についてかなり読んで理解しましたが、これについては知らないと思います。また、この用語を使用して Web 上で多くのリソースを見つけることもできませんでした。
ページに提案論文へのリンクがあります: N2439 (*this への移動セマンティクスの拡張)、そこから多くの例を得ることもできません。
この機能は何ですか?
clang のC++11 ステータス ページで、「*this の右辺値参照」という提案に出会いました。
私は右辺値参照についてかなり読んで理解しましたが、これについては知らないと思います。また、この用語を使用して Web 上で多くのリソースを見つけることもできませんでした。
ページに提案論文へのリンクがあります: N2439 (*this への移動セマンティクスの拡張)、そこから多くの例を得ることもできません。
この機能は何ですか?
まず、「*this の参照修飾子」は単なる「マーケティング ステートメント」です。の型は*this
決して変わりません。この投稿の下部を参照してください。この言葉遣いの方が分かりやすいですけどね。
次に、次のコードは、関数†</sup>の「暗黙的なオブジェクト パラメーター」の参照修飾子に基づいて、呼び出される関数を選択します。
// 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 修飾子(const
およびvolatile
) とref修飾子(&
および)を組み合わせることができることに注意してください&&
。
注: 多くの標準的な引用符とオーバーロード解決の説明がここにあります!
†これがどのように機能するか、そして@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 非静的メンバー関数の場合、暗黙的なオブジェクト パラメーターの型は次のとおりです。
ref-qualifierなしで、またはref -qualifier を使用して宣言された関数の「cv への左辺値参照」
X
&
ref-qualifierで宣言された関数の「cv への右辺値参照」
X
&&
ここ
X
で、 は関数がメンバーであるクラスで、cvはメンバー関数宣言の cv 修飾です。[...]p5 オーバーロードの解決中 [...] [t]暗黙のオブジェクト パラメータ [...] は、対応する引数の変換が次の追加規則に従うため、その ID を保持します。
暗黙的なオブジェクト パラメータの引数を保持するために一時オブジェクトを導入することはできません。と
型の一致を達成するためにユーザー定義の変換を適用することはできません
[...]
(最後のビットは、メンバー関数 (または演算子) が呼び出されるオブジェクトの暗黙的な変換に基づいてオーバーロードの解決をごまかすことができないことを意味します。)
この記事の冒頭にある最初の例を見てみましょう。前述の変換の後、オーバーロード セットは次のようになります。
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
に§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*
。[...]
左辺値ref-qualifierフォームの追加のユースケースがあります。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...
Lvalue ref-qualifiersは、これらの問題を解決します。
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の最終バージョンでは、以下は正しくない可能性があります。これは、最初の「この」提案からの直接値です)。
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