ムラサメ研究ブログ

主にゲームやプログラミングのログ

右辺値参照~完全転送まで100%理解するぞ! part6 Universal Referenceと std::forward

やっと UniversalReferenceですよ!

その前に復習

右辺値参照を引数にする時は あまり気にせず下記のようにするのが正しいのでしたね

#include <iostream>

using namespace std;

class X{
public:
    // explicit付けられない!
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
};

void hoge(X x){
    cout << "hoge(X)" << endl;
}

int main()
{
    X x;
    hoge(x);
    hoge(move(x));
}

呼ぶときに std::move() しなければコピーコンストラクタ、つければムーブコンストラクタが働く これは 暗黙的変換を利用し XとX&& を受け入れています

でも、コピーせず左辺値参照を渡したい事多いよね? コピーするだけ無駄だし それに STLのなかには explicit付きのものしか呼べない関数もあったりで 明示的に行いたい

そんな時は愚直に 左辺値参照と右辺値参照の関数を2つ(const参照いれて3個)実装すればよい

#include <iostream>

using namespace std;

class X{
public:
    // explicit付けられる。付けれるなら絶対付けた方がよい
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
};

void hoge(X& x){
    cout << "hoge(X&)" << endl;
}
void hoge(const X& x){
    cout << "hoge(const X&)" << endl;
}
void hoge(X&& x){
    cout << "hoge(X&&)" << endl;
}


int main()
{
    const X x;

    hoge( x );
    hoge( std::move(x) );
}

このようにすれば、当然 右辺値の時と左辺値の時で処理をわけられる

しかし、毎回同じような関数が3個。引数が増えればさらに倍増していく

それを防ぐのが Universal Referenceである

Universal Reference

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

class Hoge{
public:
    template<class T>
    void hoge(T&& x){
        // x_ = x  if lvalue
        // x_ = std::move(x) if rvale
        cout << "hoge(T&&)" << endl;
    }

private:
    X x_;
};


int main()
{
    Hoge h;
    cout << "____" << endl;
    {
        X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        h.hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        h.hoge( X() );
    }
    cout << "____" << endl;
    {
        h.hoge( std::move(X()) );
    }
    cout << "____" << endl;
}




constuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
constuct
hoge(T&%)
destuct
____
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

template を使うと、 左辺値: T=X& も 右辺値: T=X&& もどちらも受け取れる

そして 関数テンプレート void hoge(T&& x) に Tが X&の時とX&&の時を考える

ここは 前回の 参照の圧縮を使えば TがX&の時は hoge(X& x) 

TがX&&の時は hoge(X&& x) となる

ところが問題はコメント部分である 引数 xが 左辺値(X&) の時は copy assignment(X& operator =(X&)) を行いたいが 右辺値(X&&)の時は move assignment(X& operator =(X&&)) を行いたい

そんな時に使えるのが std::forward である。 中身は省略し std::forwardとは、moveと同じく 単なるキャストである std::moveが 右辺値にキャストする事に対し std::forward は、Tの型にキャストを行う

実際に使ってみる

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

class Hoge{
public:
    template<class T>
    void hoge(T&& x){
        x_ = std::forward<T>(x);
        cout << "hoge(T&&)" << endl;
    }

private:
    X x_;
};


int main()
{
    Hoge h;
    cout << "____" << endl;
    {
        X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        h.hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        h.hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        h.hoge( X() );
    }
    cout << "____" << endl;
    {
        h.hoge( std::move(X()) );
    }
    cout << "____" << endl;
}



constuct
____
constuct
copy assignment
hoge(T&&)
destuct
____
constuct
const copy asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
constuct
move asignment
hoge(T&&)
destuct
____
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

std::forwardを使えば、T&、const T&、T&& と3つの引数のオーバーロードを1関数で処理できる

とてもすばらしい。