C++ 右辺値参照とmove

投稿者: | 2015/12/6 日曜日

右辺値参照は「あとは死ぬだけになったオブジェクトの参照」

右辺値参照がわかりづらいのでメモ&テスト

まず、生のデータNamaを定義しよう。

struct NAMA {
	char namadata[100];
};

次にこれを扱うクラスNamaHandlerを定義しよう。

struct NamaHandler {
private:
	NAMA* pN_;
public:
	NamaHandler() {
		pN_ = new NAMA;
		memset(pN_, 0, sizeof(NAMA));
	}
	~NamaHandler() {
		delete pN_;
	}
};

問題ない。コンストラクタでNAMAを初期化し、デストラクタで解放。なお例外はスルー。
しかしこのクラスには問題がある。コピーコンストラクタを定義してないので、コピーするとポインタがコピーされ、デストラクタで2重deleteになってしまう。よってコピーコンストラクタを定義しよう。

	NamaHandler(const NamaHandler& nama)
	{
		pN_ = new NAMA;
		memcpy(pN_, nama.pN_, sizeof(*pN_));
	}

アサインメントのオペレーターも定義しよう。

	NamaHandler& operator=(const NamaHandler& nama)
	{
		memcpy(pN_, nama.pN_, sizeof(*pN_));
		return *this;
	}

コピーコンストラクタはインスタンスを作成するときに呼ばれる(=を使っても)。operator=は=したとき呼ばれる。
実行しよう

int _tmain(int argc, _TCHAR* argv[])
{
	NamaHandler nh;
	NamaHandler nh2(nh);   // copy constructor
	NamaHandler nh3;
	nh3 = nh2;		// operator=
	return 0;
}

ここでNamaHanderを返す関数getNama()を定義しよう。

NamaHandler getN()
{
	NamaHandler n;
	return n;
}

全部まとめたソース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
struct NAMA {
	char namadata[100];
};
 
struct NamaHandler {
private:
	NAMA* pN_;
public:
	NamaHandler() {
		pN_ = new NAMA;
		memset(pN_, 0, sizeof(NAMA));
	}
	NamaHandler(const NamaHandler& nama)
	{
		pN_ = new NAMA;
		memcpy(pN_, nama.pN_, sizeof(*pN_));
	}
	NamaHandler& operator=(const NamaHandler& nama)
	{
		memcpy(pN_, nama.pN_, sizeof(*pN_));
		return *this;
	}
	~NamaHandler() {
		delete pN_;
	}
};
 
 
NamaHandler getN()
{
	NamaHandler n;
	return n;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
	NamaHandler nh;
	NamaHandler nh2(nh);   // copy constructor
	NamaHandler nh3;
	nh3 = nh2;		// operator=
 
	NamaHandler nh4(getN());
	NamaHandler nh5;
	nh5 = getN();
	return 0;
}

このコードには論理的に問題はないが、やたらとコピーが呼ばれる。上の42行目と44行目でやたらと呼ばれる、しかも作ったオブジェクトをすぐ捨てていてもったいない。そこで右辺値参照を定義してみる。

	NamaHandler(const NamaHandler&& nama)
	{
		pN_ = new NAMA;
		memcpy(pN_, nama.pN_, sizeof(*pN_));
	}
 
	NamaHandler& operator=(const NamaHandler&& nama)
	{
		pN_ = new NAMA;
		memcpy(pN_, nama.pN_, sizeof(*pN_));
		return *this;
	}

&を&&に置き換えただけだが、実行してみるとここに来ることがわかる。どういう条件でくるのか?
参照があとは死ぬだけであとはデストラクタが動くだけのオブジェクトの参照のときに呼ばれる。
これを生かして実装を変えてみよう。

	NamaHandler(NamaHandler&& nama)
	{
		pN_ = nama.pN_;
		nama.pN_ = NULL;
	}
	NamaHandler& operator=(NamaHandler&& nama)
	{
		delete pN_;
		pN_ = nama.pN_;
		nama.pN_ = NULL;
		return *this;
	}

&&で受け取ったオブジェクトのコピーをせずにポインタだけコピーしている。nameの方はNULLをセットしてデストラクタが呼ばれても大丈夫なようにしている。

と思ったが上のコードにはひとつ問題が。ある?
thisと&&が同じ場合のときを考えていなかったがそんなことはあるだろうか?ここではないとしてスルー

std::move()

上記の例は全部コンパイラが勝手に呼んでくれたものだった。
コンパイラは死ぬだけになったオブジェクトを判別できれば呼んでくれるが、すべてのものを判別できるとは限らない。

1
2
3
4
5
6
NamaHandler getN2()
{
	NamaHandler n1;
	NamaHandler n2(n1);
	return n2;
}

4行目のn1はもう使ってないので&&を呼んでくれてもいいのに呼んでくれない。そこで書き換えよう。

NamaHandler getN3()
{
	NamaHandler n1;
	NamaHandler n2(std::move(n1));
	return n2;
}

ここまで全部のソース

struct NAMA {
	char namadata[100];
};
 
struct NamaHandler {
private:
	NAMA* pN_;
public:
	NamaHandler() {
		pN_ = new NAMA;
		memset(pN_, 0, sizeof(NAMA));
	}
	NamaHandler(const NamaHandler& nama)
	{
		pN_ = new NAMA;
		memcpy(pN_, nama.pN_, sizeof(*pN_));
	}
	NamaHandler(NamaHandler&& nama)
	{
		pN_ = nama.pN_;
		nama.pN_ = NULL;
	}
	NamaHandler& operator=(const NamaHandler& nama)
	{
		memcpy(pN_, nama.pN_, sizeof(*pN_));
		return *this;
	}
	NamaHandler& operator=(NamaHandler&& nama)
	{
		delete pN_;
		pN_ = nama.pN_;
		nama.pN_ = NULL;
		return *this;
	}
	~NamaHandler() {
		delete pN_;
	}
};
 
 
NamaHandler getN()
{
	NamaHandler n;
	return n;
}
 
NamaHandler getN2()
{
	NamaHandler n1;
	NamaHandler n2(n1);
	return n2;
}
 
NamaHandler getN3()
{
	NamaHandler n1;
	NamaHandler n2(std::move(n1));
	return n2;
}
 
int test()
{
	NamaHandler nh;
	NamaHandler nh2(nh);   // copy constructor
	NamaHandler nh3;
	nh3 = nh2;		// operator=
 
	NamaHandler nh4(getN());
	NamaHandler nh5;
	nh5 = getN();
	nh5 = getN2();
	nh5 = getN3();
	return 0;
}

std::moveはよっぽど特殊じゃないと使わない気がするし間違うとやばい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です