web-dev-qa-db-ja.com

定数参照と移動セマンティクス

C++ 11以降、パラメーターでconst参照を使用する必要がある状況を知りました。私は移動のセマンティクスを完全には理解していませんが、これは正当な質問だと思います。この質問は、値を「読み取る」だけでよいのに、const参照が作成中のコピーを置き換える状況のみを対象としています(例:constメンバー関数の使用法)。

通常、私はこのような(メンバー)関数を書きます:

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(const T& value) {
         _impl.Push_back(value);
    }
};

しかし、私が次のように記述し、class T ofcourseがmoveコンストラクターを実装する場合、コンパイラーがmoveセマンティクスを使用して最適化すると仮定すると安全だと思います。

#include <vector>

template<class T>
class Vector {
    std::vector<T> _impl;
public:
    void add(T value) {
         _impl.Push_back(value);
    }
};

私は正しいですか?その場合、どのような状況でも使用できると想定しても安全ですか?そうでない場合、私はどちらを知りたいのです。これにより、たとえば、基本型のクラス特殊化を実装する必要がなくなるため、作業がはるかに簡単になります。

36
Tim

あなたが提案するソリューション:

_void add(T value) {
     _impl.Push_back(value);
}
_

この方法では、右辺値をadd()(左辺値を渡すと2つのコピー)を渡しても、常にvalueの1つのコピーが実行されるため、ある程度の調整が必要になります。valueが左辺値なので、 _Push_back_に引数として渡しても、コンパイラは自動的に移動しません。

代わりに、これを行う必要があります。

_void add(T value) {
     _impl.Push_back(std::move(value));
//                   ^^^^^^^^^
}
_

これは良い方法ですが、Tの移動が安価か高価かがわからないため、テンプレートコードには十分ではありません。 Tが次のようなPODの場合:

_struct X
{
    double x;
    int i;
    char arr[255];
};
_

次に、それを移動することは、それをコピーすることよりも速くはありません(実際、移動することは同じことコピーすることと同じです)。ジェネリックコードは不要な操作を回避するようになっているため(また、一部のタイプではこれらの操作にコストがかかる可能性があるため)、値でパラメーターを取ることはできません。

可能な解決策の1つ(C++標準ライブラリで採用されているもの)は、add()の2つのオーバーロードを提供することです。1つは左辺値参照、もう1つは右辺値参照です。

_void add(T const& val) { _impl.Push_back(val); }
void add(T&& val) { _impl.Push_back(std::move(val)); }
_

もう1つの可能性は、いわゆるniversal referenceを受け入れるadd()の(おそらくSFINAEに制約された)完全転送テンプレートバージョンを提供することです。 ):

_template<typename U>
void add(U&& val) { _impl.Push_back(std::forward<U>(val)); }
_

これらのソリューションはどちらも、左辺値が指定されている場合は1つのコピーのみが実行され、右辺値が指定されている場合は1つの移動のみが実行されるという意味で最適です。

35
Andy Prowl