2014年4月30日水曜日

std::vectorとコピーコンストラクタと仮想関数

#include <iostream>

struct Value
{
	int v;
	Value()
	: v(32)
	{}
	~Value()
	{
		v = 0;
	}
	void Func()
	{
		std::cout << v << std::endl;
	}
};

int main()
{
	Value* p;
	{
		Value a;
		p = &a;
		p->Func();
	}
	p->Func();
	return 0;
}
最初のFuncの呼び出し時は、aは解体されていないので32が出力される。
二回目のFuncでは、aは解体済みなので、エラーになるか、または0が出力される。
#include <iostream>

struct ISample
{
	virtual void Func() = 0;
};

struct Sample : public ISample
{
	void Func()
	{
		std::cout << "Sample::Func" << std::endl;
	}
};

int main()
{
	ISample* p;
	{
		Sample s;
		p = &s;
	}
	p->Func();
	return 0;
}
main関数内のブロックを抜けた時点で、sのデストラクタが呼ばれ、解体される。
Funcを呼び出した時点で、仮想関数テーブル内のデータが失われていた場合、
「純粋仮想関数がコールされた」という、初学者にとっては見慣れないエラーとなって現出する。
#include <iostream>
#include <vector>

struct ISample
{
	virtual void Func() = 0;
};

struct Sample : public ISample
{
	void Func()
	{
		std::cout << "Sample::Func" << std::endl;
	}
};

struct Object
{
	ISample* p;
	Sample s;
	Object()
	: p(&s)
	{}
};

int main()
{
	Object o;
	o.p->Func();
	
	std::vector<Object> ov;
	ov.push_back(Object());
	ov[0].p->Func();
	
	return 0;
}
最初のFuncの呼び出しは問題ない。
二回目、vectorへの要素の追加が問題。
push_back関数の実引数の部分で、まず無名ローカル変数としてObjectのインスタンスが生成される。
そして、vectorが確保している領域へ、コピーコンストラクタによってコピーされる。
ここでコピーコンストラクタが用いられることが問題。
私は、てっきり自分で定義したコンストラクタでvector内の領域が初期化されると思っていたが、
実際にはコピーコンストラクタが呼ばれるので、コピーされたpが指すアドレスは、
コピーされた先のアドレスではなく、元となった無名ローカル変数内のsのアドレスとなる。
このオブジェクトを後で使おうとすると、エラーの原因となる。
もしエラーが出ても、「純粋仮想関数がコールされた」という初学者にとっては一見意味不明なエラーとなる。
このエラーを特定するのに3日近くかかってしまった。

0 件のコメント: