読者です 読者をやめる 読者になる 読者になる

C++における「コピー」と「移動」、ムーブセマンティクスへ至る病

まずムーブセマンティクスとは何か、ということは忘れます。
C++における「オブジェクトのコピーと移動」について考えます。
C++にはオブジェクトに対して「コピー」と「移動」という概念があります。
一般的なのは「コピー」です。
お馴染みの「コピーコンストラクタ」がそれです。

class CopyConstructor
{
public:
	CopyConstructor() {
		cout << "CopyConstructor()" << endl;
	}
	
	CopyConstructor(const CopyConstructor& rhs) {
		cout << "CopyConstructor(const CopyConstructor& rhs)" << endl;
	}
	
	~CopyConstructor() {
		cout << "~CopyConstructor()" << endl;
	}
};

int main()
{
	CopyConstructor c1;
	
	CopyConstructor c2(c1);
	
	return 0;
}

この結果が、

CopyConstructor()
CopyConstructor(const CopyConstructor& rhs)
~CopyConstructor()
~CopyConstructor()

これです。
要するに「オブジェクトを複製するとき」に呼び出されるのが「コピーコンストラクタ」です。

では、「移動」はなんでしょう?
C++で「移動」を受け持ち、「コピーコンストラクタ」に対応するのが「ムーブコンストラクタ」です。
つまり「オブジェクトを移動するとき」に呼び出されるべきなのが「ムーブコンストラクタ」です。


で、重要なのが
「コピーは複製なので所有権を委譲できず、同一のオブジェクトを作らなければならない」
が、
「ムーブは移動なので所有権を委譲でき、同一のオブジェクトを作るのではなく所有権を移動する(しなければならない)」
ということです。
よって、移動の方がコストが軽い、ということです。
では、「所有権の移動」とはどういうことでしょう?

単純なオブジェクトでかつ、コピーコンストラクタをムーブコンストラクタとして実装してみます。

class MoveConstructor
{
public:
	MoveConstructor()
	: ownershipObject_(new int(100))
	{
		cout << "MoveConstructor()" << endl;
	}
	
	MoveConstructor(MoveConstructor& rhs)
	: ownershipObject_(rhs.ownershipObject_)
	{
		rhs.ownershipObject_ = 0;
		cout << "MoveConstructor(const MoveConstructor& rhs)" << endl;
	}
	
	~MoveConstructor() {
		cout << "~MoveConstructor()" << endl;
		delete ownershipObject_;
	}
	void print() {
		if (ownershipObject_) {
			cout << *ownershipObject_ << endl;
		} else {
			cout << "Nothing Ownership" << endl;
		}
	}
private:
	int* ownershipObject_;
};

これが簡単なムーブコンストラクタ(所有権移動コンストラクタ)を持つオブジェクトです。
コンストラクションされたオブジェクトは、「ムーブコンストラクタ(言語機能でのコピーコンストラクタ)」によって、
内部オブジェクトの所有権を譲り渡すことになります。

int main()
{	
	MoveConstructor m1;
	
	m1.print();
	
	MoveConstructor m2(m1);
	
	m1.print();
	m2.print();
	
	return 0;
}

この結果は、

MoveConstructor()
100
MoveConstructor(const MoveConstructor& rhs)
Nothing Ownership
100
~MoveConstructor()
~MoveConstructor()

です。
m1の持っていた所有権が「ムーブコンストラクタ」を呼び出したことで、
m2に対して移動していることがわかりますね。
さて、なぜここでは「コピーコンストラクタ」で「ムーブコンストラクタ」を実装したのでしょう?


答えは、C++はまだ「ムーブコンストラクタ」というものが存在しないからです。
この「ムーブコンストラクタ」はC++0xにおいて「右辺値参照(rvalue reference)」といった概念で実現されます。
「右辺値参照」ってなんですか?
という疑問はまず置いておいて、憶えるべきことは、
「コピー」は「コピーコンストラクタ」、オブジェクトを複製する。故にコストが高い。
「移動」は「ムーブコンストラクタ」、オブジェクトの移動する。故にコストが安い。
ということです。


こうきくと、今のC++になぜ「ムーブコンストラクタ」が無いのか不思議に思いますが、まずは「移動」と「コピー」の違いを憶えることが大事です。
この考えを利用した標準的な実装が「std::auto_ptr」です。
これは「移動」の概念を用いてポインタの所有権を移動させるとても標準的な実装です。
この実装はコピーコンストラクタを用いて実装されているため、
std::auto_ptrはコピーすることができません。
(続く)