Item25 Use std::move on rvalue reference, std::forward on universal references


右値参照は移動可能なオブジェクトにのみバインドされます.右値参照のパラメータがある場合は、このパラメータが移動可能なオブジェクトを受信するために使用されていることを知っておく必要があります.一般的な参照は、右値にバインドするか、左値にバインドすることができます.次にstd::forwardは、入力された値のタイプに応じて条件付きのタイプ変換を行うことができ、入力された値が右の値である場合にのみ右の値に変換され、そうでない場合は左の値に変換され、std::moveは無条件に右の値に変換され、この2つの関数の違いがItem 23で詳細に説明されている.ここでは、std::moveまたはstd::forwardを呼び出す場合について詳細に説明します.
汎用参照にstd::moveを使用する
前の記事ではstd::moveは左の値に対して使用でき、無条件に右の値に変換できることを知っていますが、std::moveは共通の参照に対して使用できますか?実験によってこれが可能であることを検証することができますが、あまりよくない行為を招き、プログラムに奇妙な問題が発生しやすくなります.例えば、次の例です.
class Widget {
public:
    template<typename T>
    void setName(T&& newName) {     //      
        name_ = std::move(newName);
    }

private:
    std::string name_;
};


Widget w;
std::string n  = "test";
w.setName(n);
std::cout << n << std::endl;

  上のプログラムはsetNameを呼び出した後、nの値は空になりました.setNameの内部はstd::stringに対してmoveを呼び出したため、std::stringの移動構造関数が呼び出され、その内部データがWidgetのメンバー変数name_に移動されました.自分自身のデータが空になった.setNameを文字通り理解するには,この方法はユーザが入力したパラメータを修正すべきではない.しかし、上のコードは明らかにこの考えに反している.しかし、ユーザーはデータのコピーを避けたい場合もあります.正しい方法は、このデータコピーを回避する操作をユーザに任せて開始し、std::moveをユーザに自発的に呼び出させるべきであるため、以下のような比較的妥当な実現がある.
class Widget {
public:
    void setName(const std::string& newName) {  //   1
      name_ = newName;
    }
    void setName(std::string&& newName) {       //   2
      name_ = std::move(newName);
    }
 private:
    std::string name_;
};
Widget w;
std::string n  = "test";
w.setName(n);   //          setName
std::cout << n << std::endl;    //   
w.setName(std::move(n));    //           setName

  上記のバージョンは比較的包括的で、ユーザーが入力したパラメータを変更したくない場合は最初のバージョンを使用すればよい.内部でデータコピーを行うとユーザーが入力したパラメータは変更されず、ユーザーがデータコピーを回避したい場合は、受信したパラメータに対してstd::moveをアクティブに呼び出し、2番目のバージョンのsetNameを呼び出すとデータコピーを回避できる.しかし、上記のコードはやや肥大化しており、各メンバー変数に対してsetメソッドを実装するには2つのバージョンを書く必要があります.複数のデータ・メンバーを持つオブジェクトには、setシリーズのリロード方法を大量に書く必要があります.これに加えて、汎用参照のバージョンに比べて、ユーザがw.setName("test")を呼び出すと、汎用参照であればstd::stringの付与操作によって内部のname_に直接付与され、しかし、上記のバージョン2では、std::string一時変数を構築し、内部のname_に2回移動する必要があります.変数、最後の一時変数は、汎用参照のバージョンよりもオーバーヘッドが大きいプロファイル操作を行います.汎用リファレンスのバージョンでも、通常のバージョンでも、右値リファレンスのバージョンでも、いくつかの問題があるかもしれませんが、果たして良い案があるのでしょうか.これが本稿で議論する話題std::forward on universal referencesであり、汎用リファレンスにはstd::forwardを優先的に使用する必要があります.
汎用参照にstdを使用する::forward
上記の未解決の問題については、std::forwardおよび汎用参照によって完璧に解決できます.
class Widget {
public:
    template<typename T>
    void setName(T&& newName) {     //      
        name_ = std::forward(newName);
    }

private:
    std::string name_;
};
Widget w;
std::string n  = "test";
w.setName(n);   //n   ,  std::forward(newName)       
std::cout << n << std::endl;    //   
w.setName(std::move(n));    //       ,  std::forward(newName)      

関数に返される左の値にstd::moveを使用
  関数が左の値を返す場合は、左の値にstd::moveを使用することで、コードをより効率的にすることができます.以下のようにします.
Widget operator+(Widget&& lhs, const Widget& rhs) {
  lhs += rhs;
  return std::move(lhs);    
}

​   如果上面不使用std::move的话,那么在编译器不进行任何优化的前提下,lhs会首先拷贝给一个临时变量接着lhs析构,然后这个临时变量再进行拷贝构造给函数的返回值接收者,最后临时变量析构,这样的一个小小的操作居然会导致两次拷贝操作,开销如此之大,std::moveを使用すると、コピー操作が1回省けます.この関数を呼び出した場所で再び移動操作を使用すると、コピー操作が省けます.
注意:通常、上記の2回のコピー操作が必要で、効率は高くありませんが、通常、コンパイラはRVO最適化を行い、効率は非常に高いです.
  コンパイラは、RVOの最適化によってstd::moveよりも効率的であり、戻るオブジェクトが移動の意味を持たない場合、std::moveを使用すると、コピーコンストラクタが呼び出され、コピーのオーバーヘッドが節約されません.現在のコンパイラではstd::moveを使用したオブジェクト(return std::move(lhs))のRVO最適化はできず、IBMのZhan Wuはstd::moveとRVOの対比に関する文章を発表した.