質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.82%

C++ ムーブについて

解決済

回答 5

投稿

  • 評価
  • クリップ 2
  • VIEW 2,228

strike1217

score 585

ムーブについてなのですが・・・

std::move()は以下のように定義されているようです。
正確にはもう少しわかりにくい記述をしているのですが、GDBでも確認できます。

template<typename T>
decltype(auto) move(T&& Param){
     using ReturnType = remove_reference_t<T>&&;
     return static_cast<ReturnType>(Param);
}


右辺値参照にキャストしているということは、返り値は右辺値ですか?。
・・・??

これでムーブ・・・移動していることになるんですか?
これはただの「右辺値化」ですよね??

右辺値とは名前をもたない一時的なオブジェクトである。

という情報を目にしました。
ということは、ムーブと言うよりは「右辺値化」「一時オブジェクトの生成」ということですよね?

++++++++++++++++++++++++++++++++++++++++++++++++++

疑問なのは、
・なぜこれがコピーより効率が良いと言われているのか??
・なぜこれをムーブと呼ぶのでしょうか??

++++++++++++++++++++++++++++++++++++++++++++++++++

ただの右辺値化ごときで、コピーより高速になる??というのはよく分かりません。

moveの必要性においても疑問がありますね。
例えば、よく使う所有権の移動についてです。

#include<iostream>
#include<memory>

void test_func(){
    std::unique_ptr<int> ptr(new int);
    std::unique_ptr<int> wtr(new int);

    *wtr = 20;

    std::cout << *wtr << std::endl;
    ptr = std::move(wtr);

    *ptr = 40;
    std::cout << *ptr << std::endl;
}

int main(){
    test_func();

    return 0;
}


まぁ・・・普通ですが・・・
この ptr = std::move(wtr); の所有権を移動するときですね。

(ここでも・・・一時オブジェクトを生成しているんですかね? イマイチ一時オブジェクトが生成される場所を把握していなんですが・・・)

GDBで追って見ました。

operator=(unique_ptr&& __u) noexcept {
     reset(__u.release());
     get_deleter() = std::forward<deleter_type>(__u.get_deleter());
     return *this;
}


これは・・・std::moveが所有権の移動をしているのではなく、std::unique_ptr側で所有権の移動を定義しているんですよね。

moveの定義を見るとただのキャストです。
なので、ムーブを使わなくてもキャストで同じことができるはずです。

 std::unique_ptr<int> str(new int);
 str = static_cast<std::unique_ptr<int>&&>(ptr);

 std::cout << *str << std::endl;


できます。
問題なく動作しています。

つまり・・・
このムーブというのは「文法上のだたの見せかけ」に過ぎないのではないでしょうか?

内部動作的に、「ムーブというものは存在しない」ということだと思うのですが・・

そもそもムーブって必要なんでしょうかね??
右辺値化するだけなのであればキャストで良いのでは??

かえってムーブなどあるとわかりにくさが増しそうですが・・・

(ああ、ちなみに、std::unique_ptrのデストラクタは所有権を移動する時に呼ばれるのではなく、関数終了時に呼ばれていました。
所有権を移動して、もう使わなくなったのなら、その時点でデストラクタを呼び出しても良いような気がしますが・・・)

分かる方いたら教えてください。
ムーブと一緒に出てくる右辺値参照については、再度質問いたします。
GCC Linux です。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+7

C++の「ムーブ・セマンティクス」に関して、典型的な勘違いがいくつかあるように見受けられます。

手前味噌ですが、まずは下記の記事を読んでみてください。

後者より引用:

std::move関数 それ自身は、何の処理も行いません
std::move関数は、型キャスト(type cast) しか行いません。
- 実際の "ムーブ操作" 処理を行うのは、構築/代入対象となるクラス自身です。
- 関数名moveが示すとおり、「ムーブセマンティクス」を ソースコード上で明示するラベル に過ぎません。
- C++文法上は、明示的な型キャストにより "ムーブ操作" を行えます。std::move関数は必須ではありませんが、 分かりやすさのため 利用が強く推奨されます。


右辺値参照にキャストしているということは、返り値は右辺値ですか?
これはただの「右辺値化」ですよね?? 

はい。std::move関数それ自身は、左辺値から右辺値に変換するだけです。つまり、本質的には たんなる型変換 であり、それ以上でもそれ以下でもありません。

これでムーブ・・・移動していることになるんですか?

いいえ。std::move関数のみでは何の効果もありません。対象クラスのムーブコンストラクタやムーブ代入演算子と、std::move関数を 組み合わせて利用 したときに、初めてムーブ処理が行われます。

C++では引数に右辺値参照型(T&&)をとるものを、ムーブコンストラクタやムーブ代入演算子と呼びます。対照的に左辺値参照型(const T&)をとるものは、コピーコンストラクタやコピー代入演算子となります。


これは・・・std::moveが所有権の移動をしているのではなく、std::unique_ptr側で所有権の移動を定義しているんですよね。 
なので、ムーブを使わなくてもキャストで同じことができるはずです。 

いずれも、正しい解釈です。

このムーブというのは「文法上のだたの見せかけ」に過ぎないのではないでしょうか?

思考順が逆のように思えます。ムーブという振る舞いを表現するために、あらたに必要となる文法上の仕組みが右辺値参照です。

そもそもムーブって必要なんでしょうかね??

必要です。前掲の解説記事にゆずります。

右辺値化するだけなのであればキャストで良いのでは?? 

一般には、明示的な右辺値参照型へのキャストを行うよりも、std::moveと短く記述した方が好まれると考えられており、C++標準ライブラリは設計されています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/06 19:30 編集

    > 右辺値参照って他にも使い道があるんですか?

    あるといえば、あります。例えば「完全転送(perfect forwarding)」でも右辺値参照型が利用されます。またごく一部(std::ref/cref)の「仕様上、右辺値を受け取るべきでない関数」でも右辺値参照型が利用されます。

    ただまあ、完全転送の方では別名"forwarding reference"が付けられていることもあり、先にムーブセマンティクスを理解し、使いこなせるようになってからの方が混乱せずに済むとは思いますよ(私見)

    キャンセル

  • 2018/03/06 19:40

    ああ、完全転送ですね。よくムーブの話と一緒にでてきますが・・・

    右辺値参照ってそんなに便利な代物でしょうか?
    「右辺値を束縛できる参照型」と理解しているのですが・・・
    これがあることのメリットがイマイチよく分かりませんね。

    int&& a = 10; これだけなら、普通の変数でもできますし
    int a = 10;

    「ある一部の機能を実現するためだけに用意されたもの」という感じがしてなーーんか大物感がないですね。

    キャンセル

  • 2018/03/06 20:32 編集

    便利か否かではなく、ムーブセマンティクス(ほか)を表現するために必要というものですね。そもそも必要性がなければ無理に使うものではありません。あなたが必要に思えなければ使わなくても良く、一部はライブラリやコンパイラ側で宜しくやってれます。

    プログラミング言語は表現の道具です。ある言語機能にはそれぞれ目的があり、ただそれだけのことです。(当初は意図しない応用が後から編み出されることはありますけど)

    キャンセル

+1

ムーブと一緒に出てくる右辺値参照については、再度質問いたします。

本質的に、「ムーブ」は「右辺値参照」を作り出すための操作なので、分けて考えても意味がありません。

これは・・・std::moveが所有権の移動をしているのではなく、std::unique_ptr側で所有権の移動を定義しているんですよね。

つまり、「もういらなくなる値」という情報を、右辺値参照というとして表現することで、「ムーブコンストラクタ」や「ムーブ代入演算子」など、引数の型が違うオーバーロードとして、「std::unique_ptr側で所有権の移動を定義」できるようになるわけです。

std::moveは何も実質的なことをしているわけではなく、「右辺値参照=もういらなくなる値」という型にキャストしているだけです。

シンプルな配列クラスを使って「右辺値参照」と「ムーブセマンティクス」を知る

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

こんにちは。

std::moveはおっしゃる通り単なるキャストです。何のために使うのかというとオーバーロードされた関数を呼び分けるために使います。

ムーブとコピーのコンストラクタを両方用意することがありますが、キャストすることで、それらを呼び分けるわけです。

#include <iostream>
class Foo
{
    int* data;
public:
    Foo(int init) : data(new int(init)) { }
    ~Foo() { delete data; }
    Foo(Foo const& iRhs) : data(new int(*(iRhs.data))) { }
    Foo(Foo&& iRhs) : data(iRhs.data) { iRhs.data = nullptr; }
    void print(char const *iTitle)
    {
        if (data)
            std::cout << iTitle << " *data=" << *data << "\n";
        else
            std::cout << iTitle <<  " data is nullptr\n";
    }
};

int main()
{
    Foo aFoo0(123);
    aFoo0.print("aFoo0");

    Foo aFoo1=aFoo0;
    aFoo0.print("aFoo0");
    aFoo1.print("aFoo1");

    Foo aFoo2=std::move(aFoo0);
    aFoo0.print("aFoo0");
    aFoo2.print("aFoo2");
}


実行結果:

aFoo0 *data=123
aFoo0 *data=123
aFoo1 *data=123
aFoo0 data is nullptr
aFoo2 *data=123


wandbox

このような仕組みですから、ムーブ・コンストラクタでコピーしたり、その他の変なことをすることだって(ムーブせずに元を破壊するだけとか)できます。しかし、本当にやっちゃうと強く非難され、人でなしと言われるかも知れません。

さて、これの何が嬉しいかというと、無条件にムーブできる時にスマートに書けることが大きいと思います。

#include <iostream>
#include <vector>
#include <string>

int main()
{
    std::vector<std::string> aList;
    aList.push_back("abcdefghijklmnopqrstuvwxyz");
    for (auto& str : aList)
    {
        std::cout << str << "\n";
    }
}


のpush_back呼び出しで、"abcdefghijklmnopqrstuvwxyz"によりstd::string型の一時オブジェクトが生成されてpush_back()関数に渡されます。
C++11以降のpush_back()は右辺値を受け取るとムーブしますので、コピーを最小限にしてくれます。(C++11ではemplace_backを使うことが多いです。しかし、yohhoyさんご指摘の通り、この例示にはemplace_backでは適切な説明にならないのでpush_backに変更しました。)

しかし、右辺値参照がなかった場合、ムーブで受け取るためには専用の関数を用意する必要があります。

void push_back_move(T& rhs);


みたいな感じです。しかし、左辺値参照は一時オブジェクト(右辺値)を受け取れません。なので、次のように呼び出す必要があります。

{
    std::string str("abcdefghijklmnopqrstuvwxyz");
    aList.push_back_move(str);
}

なかなか嫌なものです。(なお、VC++は「独自拡張」と称してクラス型については左辺値参照で右辺値を受け取ることができます。)

下記が参考になると思います。
rvalue reference 完全解説
第35回目 ムーブと右辺値参照と特殊メンバ関数と


右辺値参照にキャストしているということは、返り値は右辺値ですか?。

結論としてはその通りです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/06 19:22

    そうですか!
    わかりました。

    キャンセル

  • 2018/03/06 19:46

    ちょっと補足です。

    ムーブ自体のメリットは「所有権」を移動することでコピーの負荷などを削減できると説明されることが多いです。それはその通りなのですが、回答で説明したように、右辺値参照ではなくて非const参照を受け取るmove()関数を作っても当然ムーブできます。呼び方やコピーとの呼び分けが面倒なだけです。

    つまり、ある意味、当たり前なのですが、右辺値参照が無かったC++11より前の時代に、右辺値参照と同様な性能を出すことができなかったのか?というとそんなことはないわけです。

    右辺値参照は、性能を上げる仕組みではなく、性能を上げる記述を容易に(もしくは分かりやすくスマートに)できるようにする仕組みなのです。

    キャンセル

  • 2018/03/06 20:13

    「性能を上げる記述を容易に(もしくは分かりやすくスマートに)できるようにする仕組みなのです。」

    ほぉぉぉ・・・・
    なるほど
    そうなんですね!!

    キャンセル

+1

なぜmoveという名前になったかの経緯は知りませんが、プログラミング言語C++第4版の35.5.1 move()とfoward()にmoveという名称は適切ではなかったというような記載がされています。
参考まで。

move()は、右辺値へのキャストを行うだけのものだ。
(ソースコードなので省略)
move()は実際には何もムーブしないので、rvalue()と名付けるべきだったと、私は考えている。
引数の右辺値を生成して、そのオブジェクトはムーブ元になれる。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 14:34

    そうなんですか!
    はやり紛らわしいですよね。

    自分は、最初std::move関数単体で所有権などの移動をしているのかと思っていましたが、全然違いましたね。

    キャンセル

0

「右辺値」「左辺値」という言葉を忘れる。それらは言葉そのものが間違いだ

すでに回答が出ているので十分な気もしますが、私もQiitaに前にこの件については記事を書いているので貼っておきます。

みんなlvalueとrvalueを難しく考えすぎちゃいないかい?

要点としては

rvalueならば所有権を持たないという社会的合意が存在する  
→所有権を持たないならば対象オブジェクトをいかに書き換えようとも文句は言われない。

が前提。

move semanticsとは動的確保した領域を指すなどしている、ポインタがクラスメンバーにあるとき、このポインタを単純にコピーすること(新たにメモリー確保してポインタの指す先までcopyしたりはしない)。

moveはいつでも安全なわけではない。ではいつならmoveしていいのか?所有権を持っていないオブジェクトに対してだ。言い換えると、rvalue referenceに対してはmoveを行っても安全である。

lvalueの所有権を放棄したと偽装するにはrvalue referenceにキャストすれば良い。

しかし、キャストを毎回書くのはめんd。プログラマーは怠惰であるべきだ。

ここでrvalue referenceを返す関数の呼び出しの評価結果はrvalueである、という性質を利用する。つまりこれがstd::moveだ。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.82%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る