rvalue reference 完全解説で、rvalue referenceは、ほぼ解説した。ところが、うっかりしたことに、ひとつだけ忘れていた事項があったので、それを解説する。
それは、非静的なメンバ関数に、ref-qualifierがつけられるというものだ。例を挙げる。
struct Foo { void f() & {} void f() && {} }
いったいこれは何なのか。これは、thisに対する修飾である。thisは、C++では、歴史上の理由でポインタになっているが、本来、参照となるべきであった。thisが指し示すのは、そのクラスのオブジェクトである。
struct Foo { void f() {} } ; int main() { Foo a, b ; a.f() ;// thisは、aを指す。 b.f() ;// thisは、bを指す。 }
つまり、非静的なメンバ関数には、暗黙の内に、thisという引数が渡されていると考えても良い。実際、規格上は、そのような扱いになっている。というのも、thisもoverload resolutionの際に考慮されるのだ。C++03では、非静的なメンバ関数には、cv修飾ができる。これはなんに対する修飾かというと、thisに対する修飾である。
struct Foo { void f() { std::cout << "none" << std::endl ;} void f() const { std::cout << "const" << std::endl ;} void f() volatile { std::cout << "volatile" << std::endl ;} void f() const volatile { std::cout << "const volatile" << std::endl ;} } ; int main() { Foo a ; Foo const b ; Foo volatile c ; Foo const volatile d ; a.f() ;// none b.f() ;// const c.f() ;// volatile d.f() ;// const volatile }
コンパイラは、thisのcv修飾(cv-qualifier)を静的に決定できるので、当然、このoverload resolutionも、静的に解決出来るのである。
今回、これに参照修飾(ref-qualifier)が加わる。以下のように使える。
struct Foo { void f() & { std::cout << "*this* is lvalue reference." << std::endl ;} void f() && { std::cout << "*this* is rvalue reference." << std::endl ;} } ; // 関数の戻り値はrvalue Foo g() { return Foo() ; } int main() { Foo a ; a.f() ;// lvalue reference. std::move(a).f() ;// rvalue reference. g().f() ; // rvalue reference. }
コンパイラは、ある型がlvalueなのかrvalueなのかを、静的に決定できるので、当然、overload resolutionも静的に決定出来るのである。
ちなみに、何も書かないと、lvalue reference修飾したものとみなされる。
もちろん、cv-qualifierも、メンバ関数の型として扱われるので、同名で引数が同じメンバ関数の型は、8種類あることになる。cv-qualifierとref-qualifierは、どちらもoverload resolutionの際に考慮される。
たぶん、これでrvalue refereneに対する解説は、完全になったことと思う。
b.f();は lvalue referenceとなるのでは無いでしょうか?
ReplyDeletevoid test(Foo &&arg);
void test(Foo &arg);
があったとして、test(b)は、test(Foo &arg)の呼び出しとなるのと同様に。
また、a.f()をstd::moveのあとに呼び出していますが、これは、moveされた
データメンバへのアクセスがない(そもそもこの例ではデータメンバがないが)
から安全ということですよね?
>b.f();は lvalue referenceとなるのでは無いでしょうか?
ReplyDeleteよく考えればそうでした。修正します。
std::move()を、何かコア言語の機能のように勘違いしていませんか?
あくまでSemanticsです。
std::moveは、static_cast(a) をしているにすぎないのです。
rvalue referenceも、単なるreferenceに過ぎないのです。
Move Semanticsとは、たんにlvalueとrvalueを、movableなフラグとして使っているに過ぎないのです。
その他は、lvalue referenceの場合と、何も変わりありません。
lvalue referenceで、データメンバにアクセスしたからと言って、そのオブジェクトがその後使えなくなるとは限らないでしょう。
もちろん、参照しているわけですから、publicなメンバ変数に対して、破壊的な書き換えもできるわけです。
std::moveは、static_cast(a) をしているにすぎないのです。
ReplyDeleteうっかりangle bracketが消えてしまいました。
std::moveは、static_cast< Foo && >(a) をしているにすぎないのです。
やはり、コンパイルして確認したいものですね。
ReplyDeletegccも、まだ対応していないのです。
C++0x Support in GCC- GNU Project - Free Software Foundation (FSF)
std::moveが、static_castに過ぎない件、理解していたのですが、
ReplyDelete以下の勘違いをしたまま、コメントしてしまいました。
確か、元のサンプルは、
Foo &&b = std::move(a);
でしたよね?
私は、コメントの2点目を書いているときは、これを、以下のように勘違いしてしまいました。
Foo b = std::move(a);
この場合、Fooの右辺値参照を引数とするコンストラクタ(move constructor)
が呼ばれると思います。
そして、このような処理を行った後でも、move constructorの内容によっては、
aを継続して利用することが可能だが、プログラミングスタイルとしては、
推奨されないと思うので、より適切なサンプルの記述法があるのでは?
などと思っておりました。
いずれにしても、最初の勘違いに基づく話です。
お騒がせしました。