投稿者 tel | 2013年3月17日

C++のダウンキャスト

C++でダウンキャストするときのメモ。

ベースのクラスを継承して複数のクラスを作って、それを同じリストなどに入れて処理する場合、個々の要素を操作するのにダウンキャスをしないといけない場合がある。

static_castを使う

元のクラスが分かっている時ならstatic_castを使う。継承関係がない場合、コンパイルエラーになるのでCスタイルのキャストやreinterpret_castを使うより安全。多分、速度的なペナルティはないと思う。

dynamic_castを使う

多重継承している場合とかはdynamic_castを使わないといけない。一般的にはそれほど軽くない処理らしいのでゲームとかで速度が必要な場面で使うのは心配。

オブジェクトに型を表すメンバーを持たせておく

ベースクラスのメンバーに自分の型を持たせておいて、switchなどで振り分けてstatic_castする。あんまりスマートじゃないけどわかりやすい。メンバー変数の分だけメモリが増えてしまうが1つのオブジェクトにつき4byte程度なのでそれほど気にはならないかも。サブクラスが増えると分岐も増えてメンテナンスが大変になる。

キャスト用の機能を作る

RTTIが使えないとかいう状況もあるらしいので。書いてて楽しいが、あんまりやりすぎると可読性とか落ちるかもしれない。

こんな感じでキャストに失敗したらNULLを返すようにする。staticな変数を各クラスに持たせてその変数のアドレスを返すvirtualなメンバー関数を用意する。キャスト時にそれらのアドレスを比較して何のクラスか判別している。オブジェクトに型を表すメンバー変数を持たせておくよりはメモリの使用量は少なくなる。

#include <iostream>
#include <vector>

using namespace std;

class Base {
public:
	static const char *type;
	virtual const char *GetType() const { return type; }
};

const char *Base::type = "Base";

class SubA : public Base {
public:
	static const char *type;
	virtual const char *GetType() const { return type; }

	void PrintA() { cout << "SubA Class!" << endl; }
};

const char *SubA::type = "SubA";

class SubB : public Base {
public:
	static const char *type;
	virtual const char *GetType() const { return type; }

	void PrintB() { cout << "SubB Class!" << endl; }
};

const char *SubB::type = "SubB";

template <typename T, typename Base>
T *DynamicCast(Base *p)
{
	return p->GetType() == T::type ? static_cast<T *>(p) : NULL;
}

int main()
{
	vector<Base *> list;

	list.push_back(new Base);
	list.push_back(new SubA);
	list.push_back(new SubB);

	for(vector<Base *>::iterator i = list.begin(); i != list.end(); ++i) {
		SubA *a = DynamicCast<SubA>(*i);
		cout << (*i)->GetType() << " " << a << endl;
		if(a) a->PrintA();
	}

	return 0;
}

出力

Base 00000000
SubA 00221E00
SubA Class!
SubB 00000000

自分で作らなくても多分ライブラリとかが存在するのでそれを使ったほうがいいかもしれない。

そもそもキャストしない

キャストを頻繁にするのは設計が悪い気がするので、virtualなメンバー関数の呼び出しで代替できないか考える。ただ、ベースクラスがライブラリのものとかだとどうしてもキャストしたくなることが多い。

まとめ

とりあえずめんどくさいことしなくてもいい設計を考えるのが第一。RTTIが使える状況なら毎フレーム何十回も呼ぶとかじゃない限りdynamic_castでもよさそう。


Responses

  1. 失礼します。

    型を判別する際の速度は、
    Member変数による判定 > type_info > dynamic_cast
    です。(ちなみにMember変数による判定は、if 一個で判定している場合で、switchの場合は異なる可能性があります)
    type_infoとdynamic_castだとMicrosoft C/C++ Optimizing Compilerで最適化を使用した場合大体100倍近くの速度差がでます。

    ただし、私は他人のLibraryの設計がアレで仕方ない場合を除きDown castを使ったことはありません。木構造やGraph構造なんかは、どうしてもDown castを使いたくなる人もいるかもしれませんが、Objectの実体を、木構造やGraph構造とは別途、型別にVectorや配列に格納しておけばいいのでどうにでも回避できます。

  2. 貴重なご意見ありがとうございます。
    100倍の差があると流石に使用するのを考えてしまいますね。
    最近、携帯ゲームのプロジェクトでdynamic castが使用されている箇所がありましたが、やはり1フレームに何十回も呼び出されるようなところでは速度がボトルネックとなっていました。

  3. 確かにdyanmic_castは遅いのですが、隘路になる程遅いことってあるんですね。
    ちなみに私だとVertexBufferの扱いやTesselator隘路になる場合が殆どです。(あと私はGameじゃなくて画像処理屋です)
    Profilerで調べて取り除いてみると大体この2点の比重が極端に大きくなっていますね。

  4. 最近のゲーム機のCPUならばdynamic_castを数回呼び出す程度ならほぼ気にならない速度でした。ボトルネックとなっていたところは1つの処理の中でそもそも何十回もキャストをしてしまうというアルゴリズムが悪かったという面もありました。


コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

カテゴリー

%d人のブロガーが「いいね」をつけました。