🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

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

Q&A

解決済

3回答

1401閲覧

なぜ以下のコードはmoveされないのでしょうか

__ook

総合スコア49

C++11

C++11は2011年に容認されたC++のISO標準です。以前のC++03に代わるもので、中枢の言語の変更・修正、標準ライブラリの拡張・改善を加えたものです。

C++

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

0グッド

1クリップ

投稿2019/12/27 00:45

編集2019/12/27 02:14

cpp

1#include <iostream> 2#include <vector> 3#include <string> 4 5using namespace std; 6 7class X 8{ 9public: 10 X() = default; 11 X(X&) = default; 12 X(X&&) = default; 13 X& operator = (X&) = default; 14 X& operator = (X&&) = default; 15}; 16 17int main() 18{ 19 X x1; 20 cout << &x1 << endl; 21 22 X x_c1(x1); 23 cout << &x_c1 << endl; 24 25 X x_m1(move(x1)); 26 cout << &x_m1 << endl; 27 28 X x2; 29 cout << &x2 << endl; 30 31 X x_c2 = x2; 32 cout << &x_c2 << endl; 33 34 X x_m2 = move(x2); 35 cout << &x_m2 << endl; 36}

以上のコードを実行してみたところ、すべての変数のアドレスが違いました。
moveの場合アドレスが変わらないことを想定していました(すでに認識が間違っている?)
defaultではmoveの挙動にならないのでしょうか。

【追記】

cpp

1#include <iostream> 2#include <vector> 3#include <string> 4 5using namespace std; 6 7class X 8{ 9public: 10 vector<string> s; 11 X() = default; 12 X(X&) = default; 13 X(X&& x) noexcept : s(move(x.s)) {}; 14}; 15 16int main() 17{ 18 X x1; 19 cout << &x1.s << endl; 20 21 X x_c1(x1); 22 cout << &x_c1.s << endl; 23 24 X x_m1(move(x1)); 25 cout << &x_m1.s << endl; 26}

以上のコードでもアドレスが変わるのはなぜでしょうか…
moveを定義したつもりなのですが…

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

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

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

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

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

guest

回答3

0

ベストアンサー

こんにちは。

ムーブは特殊なコピーに過ぎません。
デフォルトのムーブ演算子は基本的にはコピーです。ただし、メンバ変数がムーブ演算子を持っていれば、そのメンバ変数は「ムーブ」されます。持っていなければコピーされます。

class Xはメンバ変数を持っていないため、単にコピーされるだけです。
サイズ0のクラスは特定の場合を除き1バイトのメモリを確保しますので、その1バイトの領域がコピーされるのだろうと思います。(この1バイトはオブジェクトに「アドレス」を割り当てるために特別に確保される領域のようです。)

例えば、仮想関数を持つクラスの場合、仮想関数テーブルを持っています。ムーブした時に仮想関数テーブルが「ムーブ」されると嫌かも知れません。まあ、この場合のムーブがなんなのかが問題ですが、よくあるムーブの実装の一つのように呼び出し元の関数ポインタをnullptrにされてはたまったものではないですね。(オブジェクトをムーブした場合でも、そのオブジェクトの仮想関数テーブルは当然ですが単にコピーされます。)

そして、ご存知のようにムーブ演算子を定義することができますね。その中で「何をやる」のもプログラマーの自由です。しかし、一般に「ムーブ」と捉えられるような処理に限定しましょうという約束事が「ムーブ・セマンティクス」と表現されています。

例えば、下記のようなこともできますが、もし、実用プログラムでやっちゃうと酷い避難を受けると思います。それが「ムーブ・セマンティクス」という約束事が意味することです。

C++

1#include <iostream> 2 3class Foo 4{ 5 int mData; 6public: 7 Foo(int iData) : mData(iData) { } 8 void operator=(Foo&& oRhs) { oRhs.mData = mData; } 9 int get() { return mData; } 10}; 11 12int main() 13{ 14 Foo a(123); 15 Foo b(456); 16 17 a=std::move(b); 18 19 std::cout << "a=" << a.get() << "\n"; 20 std::cout << "b=" << b.get() << "\n"; 21}

wandbox

投稿2019/12/27 04:55

Chironian

総合スコア23272

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

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

0

moveの場合アドレスが変わらないことを想定していました(すでに認識が間違っている?)

はい、ムーブが行われるのは、クラスの中身について、そうなるようにムーブコンストラクタ・ムーブ代入演算子を実装した場合です。

「ムーブコンストラクタ」というように、オブジェクト自体はムーブでも新規作成されます。

投稿2019/12/27 00:51

maisumakun

総合スコア145963

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

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

__ook

2019/12/27 01:08

> はい、ムーブが行われるのは、クラスの中身について、そうなるようにムーブコンストラクタ・ムーブ代入演算子を実装した場合です。 …つまり… X(X&&) = default; は何も実装しなければただのコピーということですね…? moveの挙動を期待してしまいました…
maisumakun

2019/12/27 02:17

> 以上のコードでもアドレスが変わるのはなぜでしょうか… もとの回答のとおりです。Xそのものはムーブであろうが新規作成されます。「メンバ変数」をコピーせずに済ませるような実装が可能となる、のがムーブセマンティクスです。
__ook

2019/12/27 02:24

すみません、僕の想定だと、 move(x1)で右辺値へキャストされた元のアドレスが返り、引数として受け取り、その後vectorのmoveコンストラクタが呼び出され、アドレスが変わることなくmove完了、と考えていました。 どこで間違えているのでしょうか。
maisumakun

2019/12/27 02:27 編集

> その後vectorのmoveコンストラクタが呼び出され、アドレスが変わることなくmove完了 vectorの中身はムーブされますが、vectorのガワ(ポインタはそちらを参照する)は再作成されます。
__ook

2019/12/27 02:31

あああ…なるほど…!? つまりmoveされているかどうかはアドレス参照ではわからない、ということでしょうか?
SaitoAtsushi

2019/12/27 02:53 編集

これは以前にも書いたことの繰り返しになりますが、ムーブコンストラクタはムーブの文脈を「区別できるだけ」です。 コンストラクタに与えられた引数の性質がわかるだけなんです。 ですから、ムーブのときもオブジェクトの生成自体は行われますし、ムーブ元とは違うオブジェクトです。 違うものなのでアドレスは異なります。 ちなみに、デフォルトで作成されるムーブコンストラクタはデータメンバや基底がムーブ可能であればムーブしますので、ムーブ可能な全部のメンバをムーブするだけであれば定義をいちいち書かなくても default として定義すればよいです。 std::string はムーブ可能なので、メンバがそれだけならムーブコンストラクタはデフォルトでもムーブします。
__ook

2019/12/27 02:57

理解が浅くすみません… なるほど…
guest

0

ちゃんとmoveされてますよ

cpp

1#include <iostream> 2#include <vector> 3#include <string> 4 5using namespace std; 6 7class X 8{ 9public: 10 vector<string> s = { "arikitari na sekai" }; 11 X() = default; 12 X(X&) = default; 13 X(X&& x) noexcept : s(move(x.s)) {}; 14}; 15 16int main() 17{ 18 X x1; 19 cout << &x1.s[0] << endl; 20 21 X x_c1(x1); 22 cout << &x_c1.s[0] << endl; 23 24 X x_m1(move(x1)); 25 cout << &x_m1.s[0] << endl; 26}
0x2575010 0x2576040 0x2575010

https://wandbox.org/permlink/BALKtb0Bh5iZRG1U

みんなlvalueとrvalueを難しく考えすぎちゃいないかい?
もぜひお読みいただければ。

投稿2019/12/27 03:37

yumetodo

総合スコア5852

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

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

__ook

2019/12/27 04:10

ああ…要素はちゃんとコピーされているのですね…ありがとうございます。 何度も拝見していた記事です。ありがとうございます。再度読ませていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問