2009-12-25

ref-qualifier: rvalue referenceの完全を期すための補足

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に対する解説は、完全になったことと思う。

5 comments:

  1. b.f();は lvalue referenceとなるのでは無いでしょうか?
    void test(Foo &&arg);
    void test(Foo &arg);
    があったとして、test(b)は、test(Foo &arg)の呼び出しとなるのと同様に。
    また、a.f()をstd::moveのあとに呼び出していますが、これは、moveされた
    データメンバへのアクセスがない(そもそもこの例ではデータメンバがないが)
    から安全ということですよね?

    ReplyDelete
  2. >b.f();は lvalue referenceとなるのでは無いでしょうか?
    よく考えればそうでした。修正します。

    std::move()を、何かコア言語の機能のように勘違いしていませんか?
    あくまでSemanticsです。
    std::moveは、static_cast(a) をしているにすぎないのです。
    rvalue referenceも、単なるreferenceに過ぎないのです。
    Move Semanticsとは、たんにlvalueとrvalueを、movableなフラグとして使っているに過ぎないのです。

    その他は、lvalue referenceの場合と、何も変わりありません。
    lvalue referenceで、データメンバにアクセスしたからと言って、そのオブジェクトがその後使えなくなるとは限らないでしょう。
    もちろん、参照しているわけですから、publicなメンバ変数に対して、破壊的な書き換えもできるわけです。

    ReplyDelete
  3. std::moveは、static_cast(a) をしているにすぎないのです。
    うっかりangle bracketが消えてしまいました。

    std::moveは、static_cast< Foo && >(a) をしているにすぎないのです。

    ReplyDelete
  4. やはり、コンパイルして確認したいものですね。
    gccも、まだ対応していないのです。
    C++0x Support in GCC- GNU Project - Free Software Foundation (FSF)

    ReplyDelete
  5. std::moveが、static_castに過ぎない件、理解していたのですが、
    以下の勘違いをしたまま、コメントしてしまいました。

    確か、元のサンプルは、
    Foo &&b = std::move(a);
    でしたよね?
    私は、コメントの2点目を書いているときは、これを、以下のように勘違いしてしまいました。
    Foo b = std::move(a);
    この場合、Fooの右辺値参照を引数とするコンストラクタ(move constructor)
    が呼ばれると思います。
    そして、このような処理を行った後でも、move constructorの内容によっては、
    aを継続して利用することが可能だが、プログラミングスタイルとしては、
    推奨されないと思うので、より適切なサンプルの記述法があるのでは?
    などと思っておりました。

    いずれにしても、最初の勘違いに基づく話です。
    お騒がせしました。

    ReplyDelete

You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.