web-dev-qa-db-ja.com

Std :: move()とは何ですか?また、いつ使用するべきですか?

  1. それは何ですか?
  2. それは何をするためのものか?
  3. いつ使うべきですか?

良いリンクをいただければ幸いです。

524
Basilevs

C++ 11ウィキペディアのページR値の参照と移動コンストラクタ

  1. C++ 11では、コピーコンストラクタに加えて、オブジェクトに移動コンストラクタを含めることができます。
    (そして、コピー代入演算子に加えて、それらには移動代入演算子があります。)
  2. オブジェクトの型が "rvalue-reference"(Type &&)の場合、コピーコンストラクタの代わりに移動コンストラクタが使用されます。
  3. std::move()は、オブジェクトへの移動を可能にするために、オブジェクトへの右辺値参照を生成するキャストです。

コピーを避けるための新しいC++の方法です。たとえば、移動コンストラクタを使用すると、std::vectorはデータへの内部ポインタを新しいオブジェクトにコピーするだけで、移動したオブジェクトは誤った状態のままになり、すべてのデータがコピーされるのを防ぎます。これはC++になります。

移動の意味、右辺値、完全な転送のためにグーグルを試してください。

219
Scharron

1.「それはなんですか?」

std::move()は技術的には関数ですが、実際には本当に a functionではありません。コンパイラが式の値を考慮する方法の間には一種のconverterです。

「どうしますか」

最初に注意することは、std::move()実際には何も動かさない。これは式を lvalue (名前付き変数など)から xvalue に変換する。xvalueは言う。コンパイラ

あなたは私を略奪することができます、move私が持っているもので、他の場所でそれを使用します(とにかく近いうちに破壊されるので)。 ".

つまり、std::move(x)を使用すると、コンパイラはxを共食いすることができます。したがって、もしxが例えばメモリ内にそれ自身のバッファを持っていれば - std::move()ingの後にコンパイラは代わりにそれを所有する別のオブジェクトを持つことができます。

prvalue から移動することもできます(あなたが渡している一時的なものなど)、これはめったに役に立ちません。

3.「いつ使うべきですか?」

この質問をするもう一つの方法は、「既存のオブジェクトのリソースを何のために共食いするのでしょうか」というものです。アプリケーションコードを書いているのであれば、コンパイラによって作成された一時オブジェクトをいじってしまうことはおそらくないでしょう。そのため、主に、コンストラクタ、演算子メソッド、標準ライブラリアルゴリズムのような関数など、オブジェクトが自動的に作成および破棄される場所でこれを行います。もちろん、これは経験則です。

典型的な用途は、コピーではなく、あるオブジェクトから別のオブジェクトにリソースを「移動」することです。 @Guillaumeは このページにリンクしています これには簡単な例があります:コピーを少なくして2つのオブジェクトを交換する。

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

moveを使用すると、リソースをコピーする代わりにリソースを交換できます。

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Tがサイズnのvector<int>であるときに何が起こるか考えてください。最初のバージョンでは3 * n要素を読み書きし、2番目のバージョンでは基本的にベクトルのバッファへの3つのポインタだけを読み書きします。もちろん、クラスTは移動方法を知っている必要があります。これを機能させるには、クラスにクラスTの移動代入演算子と移動コンストラクターが必要です。

149
einpoklum

オブジェクトの内容をコピーすることなく他の場所に「転送」する必要がある場合は、moveを使用できます(つまり、内容は複製されないため、unique_ptrのようにコピーできないオブジェクトで使用できます)。 std :: moveを使用すると、オブジェクトがコピーを実行せずに一時的なオブジェクトの内容を取得する(および多くの時間を節約する)ことも可能です。

このリンクは私を助けてくれました:

http://thbecker.net/articles/rvalue_references/section_01.html

私の答えが遅すぎるのであれば申し訳ありませんが、私はまたstd :: moveのための良いリンクを探していました、そして私はリンクが少し "禁欲的"の上にリンクを見つけました。

これはr値の参照に重点を置き、どの文脈でそれらを使うべきか、そして私はそれがより詳細であると思うので、ここでこのリンクを共有したかったのです。

132
Guillaume

Q:std::moveとは何ですか?

A:std::move()は、C++標準ライブラリから右辺値参照にキャストするための関数です。

単純にstd::move(t)は次のものと同等です。

static_cast<T&&>(t);

右辺値は、変数に格納されない中間関数の結果など、それを定義する式を超えて持続しない一時的なものです。

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Std :: move()の実装は、 N2027: "Rvalue参照の簡単な紹介" /にあります。

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

ご覧のとおり、std::moveは、値(T)、参照型(T&&)、または右辺値参照(T&)のいずれを指定して呼び出されてもT&&を返します。

Q:それは何をするのですか?

A:キャストとしては、実行時には何もしません。参照を右辺値として考慮し続けたいということをコンパイラに指示することは、コンパイル時にのみ意味があります。

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

それが何をするかではない:

  • 議論のコピーを作る
  • コピーコンストラクタを呼び出す
  • 引数オブジェクトを変更する

Q:いつ使うべきですか?

A:右辺値ではない引数(一時的な式)で移動の意味をサポートする関数を呼び出したい場合は、std::moveを使用してください。

これは私のために次のフォローアップの質問を懇願します:

  • 移動意味論とは何ですか?コピーセマンティクスとは対照的に、移動セマンティクスは、他のオブジェクトのメンバをコピーするのではなく、「引き継ぐ」ことによってオブジェクトのメンバを初期化するプログラミング手法です。このような「引き継ぎ」は、基本データではなくポインターまたは整数ハンドルをコピーすることによって安価に転送できるポインターおよびリソース・ハンドルの場合にのみ意味があります。

  • 移動セマンティクスをサポートするのはどの種類のクラスとオブジェクトですか?メンバーをコピーするのではなくメンバーを転送することでメリットが得られるのであれば、独自のクラスに移動セマンティクスを実装するのは開発者としてあなた次第です。移動セマンティクスを実装したら、移動セマンティクスでクラスを効率的に処理するためのサポートを追加した多くのライブラリプログラマからの作業から直接恩恵を受けます。

  • なぜコンパイラは自分でそれを理解できないのですか?あなたがそうと言わない限り、コンパイラは単に関数の別のオーバーロードを呼び出すことができません。あなたはコンパイラが関数の通常のバージョンと移動バージョンのどちらを呼び出すべきかを選択する手助けをしなければなりません。

  • どのような状況で、変数を右辺値として扱うべきであるとコンパイラーに伝えたいですか?これはテンプレートやライブラリ関数で起こる可能性が最も高いでしょう。そこでは中間結果が救われる可能性があることを知っています。

44

std :: move自体はあまり効果がありません。私はそれがオブジェクトのために移動コンストラクタを呼ぶと思ったが、実際には単に型キャストを実行するだけである。

そのため、std :: moveは、移動セマンティクスを使用するための前置きとして使用されています。移動セマンティクスは、基本的に一時オブジェクトを扱うための効率的な方法です。

オブジェクトA = B + C + D + E + F;を考えます

これは見栄えのするコードですが、E + Fは一時オブジェクトを生成します。その後、D + tempは別の一時オブジェクトを生成します。クラスの通常の各「+」演算子では、ディープコピーが発生します。

例えば

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

この関数で一時オブジェクトを作成しても意味がありません。これらの一時オブジェクトは、範囲外になると行末で削除されます。

一時的なオブジェクトを「略奪」して、次のようなことを行うためにmoveセマンティクスを使用することができます。

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

これにより、不要なディープコピーが作成されるのを防ぎます。例を参照すると、ディープコピーが行われる唯一の部分はE + Fです。残りは移動セマンティクスを使用します。移動コンストラクターまたは代入演算子も、結果をAに代入するために実装する必要があります。

28
user929404

"それはなんですか?" and "それは何をするのですか?" は上で説明されています。

"いつ使うべきか"の例を挙げます。

たとえば、大きな配列のような多くのリソースを含むクラスがあります。

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

テストコード:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

以下のように出力します。

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

std::movemove constructorと一緒に使用すると、リソースを簡単に変換できることがわかります。

std::moveは他にどこが役に立ちますか?

std::moveは要素の配列をソートするときにも役立ちます。多くのソートアルゴリズム(選択ソートやバブルソートなど)は、要素のペアを交換することによって機能します。以前は、スワップを行うにはコピーセマンティクスに頼らなければなりませんでした。これで、移動セマンティクスを使用できます。これはより効率的です。

あるスマートポインタで管理されているコンテンツを別のスマートポインタに移動したい場合にも便利です。

引用:

5
Jayhello