vector配列(←なぜかコピーされる)

Objective-Cに慣れすぎてしまったせいで、すっかりC++/ActiveBasic風のオブジェクトのインスタンス化についての感覚がどこかへ消えてしまったようです。

NSObject *object = [[NSObject alloc] init];
//何か処理
[object release];

Objective-Cでは、このような方法でしかオブジェクトのインスタンス化を行うことができません。C++のnew/deleteでインスタンスを作る方法と一緒です。

しかしC++などでは、こちらの方法でインスタンス化をすることでしょう。ただ単に、クラス名を型に持ってきて、変数を宣言する方法です。

MyObject object;

それで、このタイプのインスタンスであると、関数に値を渡すときに、引数に新しくオブジェクトが作成されるわけです。作成といってもコピーですが。

例えばこんなクラスを定義して調べてみます。コピーコンストラクタが呼ばれたときに、カウントを1増やすオブジェクトです。

#include <iostream>

class Object {
public:
	int count;

	Object()
	{
		count = 0;
		std::cout << "construct object " << count << std::endl;
	}

	~Object()
	{
		std::cout << "destroy object " << count << std::endl;
	}

	Object(const Object& object)
	{
		count = object.count+1;
		std::cout << "copy construct " << count << std::endl;
	}

	void print()
	{
		std::cout <<"print " << count << std::endl;
	}
};

このクラスを使い、適当に関数を作って実行してみます。

void fx(Object anObject)
{
	anObject.print();
}

int main(int argc, char *argv[])
{
	Object object;
	fx(object);
	return 0;
}

実行結果:

construct object 0
copy construct 1
print 1
destruct object 1
destruct object 0

当然ですね。引数に値コピーするので、コピーコンストラクタが1回呼ばれます。引数を参照型にすれば、コピーされることはありません。

さて、ここまでは前置きです。ここでSTLのlist配列に入れて検証してみましょう。

int main(int argc, char *argv[])
{
	using namespace std;

	Object object;
	list<Object> array;

	for ( int i = 0; i < = 8; i++ ) {
		array.push_back(object);
	}

	for ( list<Object>::iterator it = array.begin(); it != array.end(); ++it ) {
		(*it).print();
	}
	return 0;
}

実行結果:

construct object 0
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
copy construct 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
print 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 1
destruct object 0

これは期待通りの動作です。list配列に入れるときにオブジェクトをコピーするのは納得です。もしここで実態が同じオブジェクトを配列に入れてしまうと、簡単にバグを引き起こしかねません。

ところで、これと同じことを今度はvector配列でやってみます。

int main(int argc, char *argv[])
{
	using namespace std;

	Object object;
	vector<:Object> array;

	for ( int i = 0; i < = 8; i++ ) {
		array.push_back(object);
	}

	for ( vector<Object>::iterator it = array.begin(); it != array.end(); ++it ) {
		(*it).print();
	}

実行結果:

construct object 0
copy construct 1
copy construct 2
copy construct 1
destruct object 1
copy construct 3
copy construct 2
copy construct 1
destruct object 2
destruct object 1
copy construct 1
copy construct 4
copy construct 3
copy construct 2
copy construct 2
copy construct 1
destruct object 3
destruct object 2
destruct object 1
destruct object 1
copy construct 1
copy construct 1
copy construct 1
copy construct 5
copy construct 4
copy construct 3
copy construct 3
copy construct 2
copy construct 2
copy construct 2
copy construct 2
copy construct 1
destruct object 4
destruct object 3
destruct object 2
destruct object 2
destruct object 1
destruct object 1
destruct object 1
destruct object 1
print 5
print 4
print 3
print 3
print 2
print 2
print 2
print 2
print 1
destruct object 5
destruct object 4
destruct object 3
destruct object 3
destruct object 2
destruct object 2
destruct object 2
destruct object 2
destruct object 1
destruct object 0

listとは大きく違い、多くのコピーが発生しています。なぜこのようなコピーが発生するのかといえば、おそらくvector内部で再度メモリ確保を行ってオブジェクトを移動する際、すべてコピーされていると推測することができます。

このような動作をするから何か問題があるというわけではありませんが、vector配列でコピーが起こす意図が分かりません。おそらく、コピーを発生させないようにもvector配列は実装できると思うのですが…

コメント

  1. イグトランス より:

    vectorにどれくらいの要素を詰め込むか想定できるときには、予めreserveを呼んでおけば、無駄な再確保とそれに伴う要素のコピーを避けられます。
    あるいはポインタを要素にするというのもよくやる手です。
    ちなみに、listは連結リストなので再確保が起こり得ないというわけです。

  2. OverTaker より:

    そうですね、私もポインタを要素にするつもりでいます。
    ただ、今回疑問に思ったのは、vector配列が内部で再確保をしてコピーするときに、なぜコピーコンストラクタを呼ぶようなコピーをするのか、ということです。やはりソースコードを見るべきでしょうか。

  3. さとれん より:

    vectorはC言語配列としても使用できるように以下のように定められてます。

    where T is some type other than bool,
    then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size().

    これを満足させようとおもったらメモリブロックを再確保+コピーコンストラクタでコピーしか思いつかないです。

    コピーコンストラクタを使用する理由はコピーコンストラクタの存在理由と同じですよ。

  4. OverTaker より:

    返信遅れてしまってすみません。

    どうやら、vectorの中で再確保が起こる時に、なぜかデストラクタが呼ばれないものと勘違いしていたようです。その結果、コピーコンストラクタが不要だという、変な結論に行き着いていました。

    お騒がせしました。

コメント投稿