std :: move()とは何ですか、いつ使用する必要がありますか?


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

良いリンクは大歓迎です。


43
Bjarne Stroustrupが、「Rvalue参照の簡単な紹介
DumbCoder、


12
この質問はstd::move(T && t)、にstd::move(InputIt first, InputIt last, OutputIt d_first)関連するアルゴリズムであるも存在しstd::copyます。私が指摘するのは、最初にstd::move3つの引数を取ることに直面したときのように、他の人が混乱しないようにするためです。en.cppreference.com/w/cpp/algorithm/move
josaphatv

回答:


287

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

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

これは、コピーを回避する新しいC ++の方法です。たとえば、移動コンストラクタを使用すると、std::vectorは内部ポインタをデータへの新しいオブジェクトにコピーするだけで、移動したオブジェクトを移動元の状態のままにして、すべてのデータをコピーすることはできません。これはC ++で有効です。

移動のセマンティクス、右辺値、完全な転送のためにグーグルを試してください。


40
移動セマンティクスでは、移動されたオブジェクトを有効なままにする必要がありますが、これは不適切な状態ではありません。(理論的根拠:破壊し、機能させる必要があります。)
GManNickG 2010

13
@GMan:まあ、破壊しても安全な状態である必要がありますが、私の知る限り、他の目的で使用できる必要はありません。
Zan Lynx、2011年

8
@ZanLynx:そうです。標準ライブラリでは、移動したオブジェクトを割り当て可能にする必要がありますが、これはstdlibで使用されるオブジェクトにのみ当てはまり、一般的な要件ではありません。
GManNickG

25
-1 "std :: move()はC ++ 11での移動セマンティクスの使用方法"修正してください。std::move()移動セマンティクスを使用する方法ではありません。移動セマンティクスはプログラマーに対して透過的に実行されます。moveあるポイントから別のポイントに値を渡す唯一のキャストであり、元の左辺値は使用されなくなります。
Manu343726 2014

15
私はさらに行きます。std::moveそれ自体は「何もしない」-副作用はありません。これは、プログラマーがそのオブジェクトに何が起こるかをもはや気にしないことをコンパイラーに通知するだけです。つまり、ソフトウェアの他の部分にオブジェクトからの移動を許可しますが、移動する必要はありません。実際、右辺値参照の受信者は、データに対して何を行うか、または何を行わないかについて約束をする必要はありません。
Aaron McDaid

241

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

一方でstd::move() 、技術的機能である-私は言うでしょう、それはないです、本当に機能。これは、コンパイラーが式の値を考慮する方法の間の一種のコンバーターです。

2.「それは何をしますか?」

最初に注意すべきことstd::move() は、実際には何も動かないということです。式を左辺値(名前付き変数など)からxvalueに変換します。xvalueはコンパイラーに指示します。

あなたは私を略奪し、私が持っているものを移動し、他の場所で使用することができます(とにかく私はすぐに破壊されるので)」。

つまり、を使用std::move(x)すると、コンパイラが共食いできるようになりますx。したがってx、たとえば、メモリ内に独自のバッファがある場合- std::move()コンパイラを実行すると、代わりに別のオブジェクトがそれを所有できます。

また、prvalueから移動することもできます(一時的なものなど)。これはほとんど役に立ちません。

3.「いつ使用する必要がありますか?」

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

典型的な用途は、コピーではなく、あるオブジェクトから別のオブジェクトにリソースを「移動」することです。@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いる、と言う、vector<int>サイズのn個。最初のバージョンでは3 * n要素を読み書きし、2番目のバージョンでは基本的に、ベクトルのバッファーへの3つのポインターと3つのバッファーのサイズだけを読み書きします。もちろん、クラスTは移動の方法を知っている必要があります。これを機能させるには、クラスに移動割り当て演算子とクラスの移動コンストラクターが必要Tです。


3
長い間、これらの移動のセマンティクスについて聞いたことがありますが、それらを調べたことはありません。この説明から、与えられたものは、深いコピーではなく、浅いコピーのように見えます。
ゼブラフィッシュ2016

7
@TitoneMaurice:コピーではないことを除いて-元の値はもう使用できません。
einpoklum 2016

3
@ゼブラフィッシュあなたはもっと間違ったことはできませんでした。コピーが浅い場合、オリジナルはまったく同じ状態のままになります。移動すると、通常、オリジナルは空になるか、そうでなければ有効な状態になります。
rubenvb

17
@rubenvb Zebraは完全に間違っているわけではありません。混乱を招くエラーを回避するために、元のカナビライズされたオブジェクトは通常故意に妨害されます(たとえば、ポインターをnullptrに設定して、ポインターがもう所有していないことを通知します)。移動全体は、ソースからポインターをコピーするだけで実装されるという事実目的地への(そして、意図的に指示先で何かをすることを避けることは)確かに浅いコピーを連想させます。実際、私は、移動浅いコピーであり、オプションでソースの部分的な自己破壊が続くとまで言っています。(続き)
オービットのライトネスレース

3
(続き)この定義を許可すると(そして私はそれが好きです)、@ Zebrafishの観察は間違っていません。わずかに不完全です。
ライトネスレース、オービット

146

オブジェクトのコンテンツを別の場所に「転送」する必要がある場合は、コピーを行わずにmoveを使用できます(つまり、コンテンツは複製されないため、unique_ptrなどの一部のコピー不可能なオブジェクトで使用できます)。std :: moveを使用すると、オブジェクトがコピーを行わずに(そして時間を大幅に節約して)一時オブジェクトのコンテンツを取得することもできます。

このリンクは本当に私を助けました:

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

回答が間に合わなくて申し訳ありませんが、std :: moveへの適切なリンクも探していましたが、少し上の「オースター」の上にリンクが見つかりました。

これは、r値の参照に重点を置いており、どのコンテキストでそれらを使用する必要があるかを詳しく説明します。そのため、このリンクをここで共有したいと思います。


26
素敵なリンク。私はいつもウィキペディアの記事を見つけました、そして私が実際に意味/理論的根拠が何であるかを理解するためにそれをあなたに任せてあなたに任せて、あなたに事実を投げるので私がかなり混乱して偶然見つけた他のリンクを見つけました。コンストラクターでの「セマンティクスの移動」はかなり明白ですが、&&-valuesの受け渡しに関するこれらすべての詳細は...ではないので、チュートリアルスタイルの説明はとても良かったです。
Christian Stieber

66

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&&TT&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右辺値(一時式)ではない引数で移動セマンティクスをサポートする関数を呼び出す場合に使用してください。

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

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

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

  • コンパイラがそれ自体を理解できないのはなぜですか?コンパイラは、特に断らない限り、関数の別のオーバーロードを呼び出すことはできません。関数の通常バージョンと移動バージョンのどちらを呼び出すかをコンパイラーが選択できるようにする必要があります。

  • 変数を右辺値として扱う必要があることをコンパイラに伝えたいのはどのような場合ですか?これは、中間結果が回収される可能性があることがわかっているテンプレートまたはライブラリ関数で発生する可能性があります。


2
コメントにセマンティクスを含むコード例の大きな+1。他の上位の回答は、「move」自体を使用してstd :: moveを定義しています-実際には何も明確にしていません!---議論のコピーを作成しないことは、元の値を確実に使用できないことを意味することに言及する価値があると思います。
2018年

34

std :: move自体は実際にはあまり機能しません。私はそれがオブジェクトの移動されたコンストラクターを呼び出すと思ったが、実際には型キャスト(左辺値変数を右辺値にキャストして、その変数が引数として移動コンストラクターまたは代入演算子に渡されるようにする)を実行するだけだった。

したがって、std :: moveは、移動セマンティクスを使用する前兆として使用されます。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;
}

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

むしろ、移動セマンティクスを使用して一時オブジェクトを「略奪」し、

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

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


3
あなたは移動のセマンティクスについて話しました。質問はstd :: moveがどのように使用できるかを回答に追加する必要があります。
Koushik Shetty 2013年

2
@Koushik std :: moveはあまり機能しませんが、移動のセマンティクスを実装するために使用されます。std :: moveについて知らない場合は、おそらく移動のセマンティクスも知らないでしょう
user929404

1
「何もしない」(右辺値参照へのstatic_castのみ)実際にそれが行うこととそれが行うことは、OPが尋ねたものです。std :: moveがどのように機能するかを知る必要はありませんが、移動のセマンティクスが何をするかを知る必要があります。さらに、「しかし、移動のセマンティクスを実装するために使用されます」その逆。移動セマンティクスを知っていれば、std :: moveを理解できます。移動は移動に役立ち、それ自体が移動セマンティクスを使用します。std :: moveは、引数を右辺値参照に変換するだけです。これは、移動のセマンティクスに必要なものです。
Koushik Shetty 2013年

10
「しかし、E + Fは一時的なオブジェクトを生成します」-演算子+は右から左ではなく、左から右に移動します。したがってB+C、最初になります!
Ajay

8

"それは何ですか?" そして 「それは何をしますか?」上記で説明しました。

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

たとえば、大きな配列のようなリソースがたくさんあるクラスがあります。

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

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

引用:


0

以下は、(単純な)カスタムベクトルにstd :: moveを使用した完全な例です。

期待される出力:

 c: [10][11]
 copy ctor called
 copy of c: [10][11]
 move ctor called
 moved c: [10][11]

次のようにコンパイルします。

  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

コード:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
    std::cout << "c: " << *c << std::endl;
    auto d = *c;
    std::cout << "copy of c: " << d << std::endl;
    auto e = std::move(*c);
    delete c;
    std::cout << "moved c: " << e << std::endl;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.