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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

1089閲覧

C++ ムーブについて

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2018/03/06 07:05

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

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

C++

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

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

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

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

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

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

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

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

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

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

C++

1#include<iostream> 2#include<memory> 3 4void test_func(){ 5 std::unique_ptr<int> ptr(new int); 6 std::unique_ptr<int> wtr(new int); 7 8 *wtr = 20; 9 10 std::cout << *wtr << std::endl; 11 ptr = std::move(wtr); 12 13 *ptr = 40; 14 std::cout << *ptr << std::endl; 15} 16 17int main(){ 18 test_func(); 19 20 return 0; 21}

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

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

GDBで追って見ました。

C++

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

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

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

C++

1 std::unique_ptr<int> str(new int); 2 str = static_cast<std::unique_ptr<int>&&>(ptr); 3 4 std::cout << *str << std::endl;

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

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

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

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

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

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

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 08:39

編集2018/03/06 08:43
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/03/06 09:33

> 関数名moveが示すとおり、「ムーブセマンティクス」を ソースコード上で明示するラベル に過ぎません。 なるほど! 確かに、ただのキャストではムーブしているかどうかとても分かりにくくなりそうですね。 > 思考順が逆のように思えます。 左様ですか! 「ムーブ」を実現するための「右辺値参照」と理解して良いんですかね。
yohhoy

2018/03/06 09:41

> 「ムーブ」を実現するための「右辺値参照」と理解して良いんですかね。 はい。上記理解で良いと思います。
strike1217

2018/03/06 09:43

一般的に「コピーよりムーブの方が効率が良い、コストが安い」などと言われているのはなぜでしょうか?? ここがイマイチ、ピンと来ないですね。
strike1217

2018/03/06 10:24

右辺値参照は「ムーブのためだけ」に用意されたものなんですかね? 右辺値参照って他にも使い道があるんですか?
yohhoy

2018/03/06 10:36 編集

> 右辺値参照って他にも使い道があるんですか? あるといえば、あります。例えば「完全転送(perfect forwarding)」でも右辺値参照型が利用されます。またごく一部(std::ref/cref)の「仕様上、右辺値を受け取るべきでない関数」でも右辺値参照型が利用されます。 ただまあ、完全転送の方では別名"forwarding reference"が付けられていることもあり、先にムーブセマンティクスを理解し、使いこなせるようになってからの方が混乱せずに済むとは思いますよ(私見)
strike1217

2018/03/06 10:40

ああ、完全転送ですね。よくムーブの話と一緒にでてきますが・・・ 右辺値参照ってそんなに便利な代物でしょうか? 「右辺値を束縛できる参照型」と理解しているのですが・・・ これがあることのメリットがイマイチよく分かりませんね。 int&& a = 10; これだけなら、普通の変数でもできますし int a = 10; 「ある一部の機能を実現するためだけに用意されたもの」という感じがしてなーーんか大物感がないですね。
yohhoy

2018/03/06 11:44 編集

便利か否かではなく、ムーブセマンティクス(ほか)を表現するために必要というものですね。そもそも必要性がなければ無理に使うものではありません。あなたが必要に思えなければ使わなくても良く、一部はライブラリやコンパイラ側で宜しくやってれます。 プログラミング言語は表現の道具です。ある言語機能にはそれぞれ目的があり、ただそれだけのことです。(当初は意図しない応用が後から編み出されることはありますけど)
guest

0

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

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

投稿2018/03/06 14:21

hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/03/07 05:34

そうなんですか! はやり紛らわしいですよね。 自分は、最初std::move関数単体で所有権などの移動をしているのかと思っていましたが、全然違いましたね。
guest

0

こんにちは。

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

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

C++

1#include <iostream> 2class Foo 3{ 4 int* data; 5public: 6 Foo(int init) : data(new int(init)) { } 7 ~Foo() { delete data; } 8 Foo(Foo const& iRhs) : data(new int(*(iRhs.data))) { } 9 Foo(Foo&& iRhs) : data(iRhs.data) { iRhs.data = nullptr; } 10 void print(char const *iTitle) 11 { 12 if (data) 13 std::cout << iTitle << " *data=" << *data << "\n"; 14 else 15 std::cout << iTitle << " data is nullptr\n"; 16 } 17}; 18 19int main() 20{ 21 Foo aFoo0(123); 22 aFoo0.print("aFoo0"); 23 24 Foo aFoo1=aFoo0; 25 aFoo0.print("aFoo0"); 26 aFoo1.print("aFoo1"); 27 28 Foo aFoo2=std::move(aFoo0); 29 aFoo0.print("aFoo0"); 30 aFoo2.print("aFoo2"); 31}

実行結果:

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

wandbox

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

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

C++

1#include <iostream> 2#include <vector> 3#include <string> 4 5int main() 6{ 7 std::vector<std::string> aList; 8 aList.push_back("abcdefghijklmnopqrstuvwxyz"); 9 for (auto& str : aList) 10 { 11 std::cout << str << "\n"; 12 } 13}

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

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

C++

1void push_back_move(T& rhs);

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

C++

1{ 2 std::string str("abcdefghijklmnopqrstuvwxyz"); 3 aList.push_back_move(str); 4}

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

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


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

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

投稿2018/03/06 08:23

編集2018/03/06 09:12
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2018/03/06 08:55 編集

> emplace_back呼び出しで、"abcdefghijklmnopqrstuvwxyz"によりstd::string型の一時オブジェクトが生成 されません。関数プロトタイプをご確認ください。 https://cpprefjp.github.io/reference/vector/emplace_back.html > emplace_back()は右辺値を受け取るとムーブ emplace_backの本来の目的は、コンテナ要素型のコンストラクタ呼出しによる「直接構築」です。そのうえで、引数にTの右辺値を与えると結果的にムーブコンストラクタが選択されるという流れですね。回答中コードではconst char*型が渡されており、コピー/ムーブいずれでもありません。 C++11以降はpush_backが左辺値参照/右辺値参照でオーバーロードされますから、説明意図としてはこちらの方が相応しいと思います。
Chironian

2018/03/06 08:59

yohhoyさん。コメントありがとうございます。 あああ、確かにその通りですね。push_backに修正します。
strike1217

2018/03/06 09:39

コピーコンストラクタやムーブコンストラクタに限らずに「オーバーロードされた関数を呼び分けるため」ということですね。 つまり・・・基本的には「関数の呼び出し時」に利用されるものだと理解すれば良いんですかね。。。
strike1217

2018/03/06 09:40

オーバーロードのためだけに作られた機能なんですかね?
Chironian

2018/03/06 10:12

> オーバーロードのためだけに作られた機能なんですかね? 私はオーバーロードが最も有用と考えています。 しかし、一時オブジェクトを非const参照で受け取りたい時にも使われるようです。auto&& x=foo(); や for(auto&& elem : list)みたいな感じです。 ただ、実際にそれが有用な場面に気がついたことがない(その場面に出会っていても見落としている可能性があるという意味です)ので、こちらの有用性を私はイマイチ把握できていません。 テンプレートで型が組み込み型かクラス型か確定していないような時に使うのかもです。
strike1217

2018/03/06 10:22

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

2018/03/06 10:46

ちょっと補足です。 ムーブ自体のメリットは「所有権」を移動することでコピーの負荷などを削減できると説明されることが多いです。それはその通りなのですが、回答で説明したように、右辺値参照ではなくて非const参照を受け取るmove()関数を作っても当然ムーブできます。呼び方やコピーとの呼び分けが面倒なだけです。 つまり、ある意味、当たり前なのですが、右辺値参照が無かったC++11より前の時代に、右辺値参照と同様な性能を出すことができなかったのか?というとそんなことはないわけです。 右辺値参照は、性能を上げる仕組みではなく、性能を上げる記述を容易に(もしくは分かりやすくスマートに)できるようにする仕組みなのです。
strike1217

2018/03/06 11:13

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

0

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

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

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

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

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

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

投稿2018/03/06 07:43

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

要点としては

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

が前提。

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

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

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

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

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

投稿2018/03/06 11:34

編集2018/03/06 11:35
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問