実行時型情報を使って良いのか?

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
dom

実行時型情報を使って良いのか?

#1

投稿記事 by dom » 6年前

現在CADのようなプログラムを作っており、その中で以下のように物体を管理しています。

コード:

class Shape {
public:
    virtual ~Shape(){}
    virtual void draw() = 0;
};

class Line: public Shape;
class Surface: public Shape;
...

// 生成されたオブジェクトを保持
std::vector<Shape*> objectList; 
ここで問題なのですが、objectListからあるオブジェクトが選択されたときに適当な処理をするには
実行時の型を知る必要があります。そこでtypeidやdynamic_castを使えば良いと思うのですが
google c++スタイルガイドによるとRTTIを使うような場合には設計上の欠陥があると書かれています。
そこで、Visitorパターンを用いることにしたのですが、これにも欠点があり、それを解消したものに
Acyclic Visitorというのがあると知りました。しかし、そのコードには

コード:

class Surface{
public:
    void accept(IVisitor* v) {
        if (SurfaceVisitor* ev = dynamic_cast<SurfaceVisitor*>(v))
            v->visit(*this);
        else
            // Accept Error
    }
}
という処理が必要になります。
元々、実行時型情報を用いるのが良くないとのことですが、それを解決するためにdynamic_castを用いるのは問題ないのでしょうか?それなら始めから

コード:

void update() {
    Shape* p = GetSelectedObject();
    if (Surface* sp = dynamic_cast<Surface*>(p)) {
        // Surfaceに対する処理
    }
}
のようにすれば良いのではと思いました。
そもそも、なぜ実行時型情報を用いることが設計が悪いことになるのかあまりわかっていないのですが、
直接dynamic_castを使うのと、VisitorもしくはAcyclic Visitorを使うのとは何が違うのでしょうか?

アバター
usao
記事: 1569
登録日時: 6年前

Re: 実行時型情報を使って良いのか?

#2

投稿記事 by usao » 6年前

普通に Shape::draw()のような仮想関数にしたくない理由があって
Visitorなどを考えておられるのだと思いますが,その際,

>そこで、Visitorパターンを用いることにしたのですが、これにも欠点があり、
この欠点とは,具体的にどういったことが問題になっているのでしょうか?

dom

Re: 実行時型情報を使って良いのか?

#3

投稿記事 by dom » 6年前

返信ありがとうございます。
具体的には、現在オブジェクトの種類が定まり切っておらず作り直すことが多々あり、
Visitorのインタフェースの変更が継承先に影響を与えるところです。

それと、実際に問題に直面したというよりは、RTTIの代替案であるVisitorの改善型それ自体がそもそも問題となっているRTTIを用いていることから、それならば直接dynamic_cast等を用いても良いのではないか、なぜRTTIを用いることが設計に問題があることになる(そう思わない人もいるかもしれませんが)のか単純に疑問に思います。

アバター
h2so5
副管理人
記事: 2212
登録日時: 9年前
住所: 東京
連絡を取る:

Re: 実行時型情報を使って良いのか?

#4

投稿記事 by h2so5 » 6年前

まず、Google C++ Style Guide では RTTI の代替として Visitor パターンを提案していますが、
Visitor パターンはRTTIの代替のためだけのものではないですから、 Acyclic VisitorパターンがRTTIを使用していることについては別に不思議ではありません。

コード:

void update() {
    Shape* p = GetSelectedObject();
    if (Surface* sp = dynamic_cast<Surface*>(p)) {
        // Surfaceに対する処理
    }
}
このコードはSurfaceに対する処理が複雑化したりオブジェクトの種類が増えた場合、update関数が肥大化するという点で問題です。
それを回避するのがVistorパターンですが、対象のクラスがVisitorを知っている必要があるというデメリットがあります。
それを解決するために、RTTIの使用については妥協しているのがAcyclic Visitorパターンということになります。

アバター
usao
記事: 1569
登録日時: 6年前

Re: 実行時型情報を使って良いのか?

#5

投稿記事 by usao » 6年前

便乗質問みたいになってしまうのですが
Acyclic Visitor ってこんな感じで合ってますか?

コード:

//-------------------------------------
//ベースクラス

//class AcyclicVisitorBase
//{
//public:
//	virtual ~AcyclicVisitorBase(){}
//};
// ↓訂正↓
class AcyclicVisitorBase
{
public:
	virtual ~AcyclicVisitorBase() = 0;
};
AcyclicVisitorBase::~AcyclicVisitorBase(){}

class ElemBase
{
public:
	virtual ~ElemBase(){}
	virtual void Accept( AcyclicVisitorBase &rVisitor ) = 0;
};

//-------------------------------------
//要素Aクラス
class Visitor_A : virtual public AcyclicVisitorBase
{
public:
	virtual ~Visitor_A(){}
	virtual void Visit( class ElemA & ) = 0;
};

class ElemA : public ElemBase
{
public:
	virtual ~ElemA(){}
	virtual void Accept( AcyclicVisitorBase &rVisitor )
	{
		std::cout << "ElemA::Accept() -> ";
		if( Visitor_A *pAVisitor = dynamic_cast<Visitor_A*>( &rVisitor ) )
		{	pAVisitor->Visit(*this);	}
		else
		{	std::cout << "NOP" << std::endl;	}
	}
};

//-------------------------------------
//要素Bクラス
class Visitor_B : virtual public AcyclicVisitorBase
{
public:
	virtual ~Visitor_B(){}
	virtual void Visit( class ElemB & ) = 0;
};

class ElemB : public ElemBase
{
public:
	virtual ~ElemB(){}
	virtual void Accept( AcyclicVisitorBase &rVisitor )
	{
		std::cout << "ElemB::Accept() -> ";
		if( Visitor_B *pBVisitor = dynamic_cast<Visitor_B*>( &rVisitor ) )
		{	pBVisitor->Visit(*this);	}
		else
		{	std::cout << "NOP" << std::endl;	}
	}
};

//-------------------------------------
//Visitorの方々
class ConcreteVisitor_A : public Visitor_A
{
public:
	virtual ~ConcreteVisitor_A(){}
	virtual void Visit( ElemA & ){	std::cout << "ConcreteVisitor_A::Visit(A)" << std::endl;	}
};

class ConcreteVisitor_B : public Visitor_B
{
public:
	virtual ~ConcreteVisitor_B(){}
	virtual void Visit( ElemB & ){	std::cout << "ConcreteVisitor_B::Visit(B)" << std::endl;	}
};

class ConcreteVisitor_A_B : public Visitor_A, public Visitor_B
{
public:
	virtual ~ConcreteVisitor_A_B(){}

	virtual void Visit( ElemA & ){	std::cout << "ConcreteVisitor_A_B::Visit(A)" << std::endl;	}
	virtual void Visit( ElemB & ){	std::cout << "ConcreteVisitor_A_B::Visit(B)" << std::endl;	}
};

//-------------------------------------
//メイン
int main(void)
{
	ElemA a;
	ElemB b;

	{
		std::cout << "> ConcreteVisitor_A" << std::endl;
		ConcreteVisitor_A V;
		a.Accept( V );
		b.Accept( V );
		std::cout << std::endl;
	}

	{
		std::cout << "> ConcreteVisitor_B" << std::endl;
		ConcreteVisitor_B V;
		a.Accept( V );
		b.Accept( V );
		std::cout << std::endl;
	}

	{
		std::cout << "> ConcreteVisitor_A_B" << std::endl;
		ConcreteVisitor_A_B V;
		a.Accept( V );
		b.Accept( V );
		std::cout << std::endl;
	}

	std::cout << "[Push Enter to Quit]";
	std::cin.ignore();
	return 0;
}

Visitor_AだのVisitor_Bは,こうすればいいか.

コード:

template< class ElemType >
class Visitor_T : virtual public AcyclicVisitorBase
{
public:
	virtual ~Visitor_T(){}
	virtual void Visit( ElemType & ) = 0;
};

dom

Re: 実行時型情報を使って良いのか?

#6

投稿記事 by dom » 6年前

>>h2so5さん
確かにこれからオブジェクトの種類が増える可能性を考えると関数が肥大化するのは問題でした。
設計の良し悪しでいえばこの場合についてはRTTIの使用は問題ないと考えられそうです。

>>usaoさん
そんな感じであっていると思います。
単なるVisitorに比べてテンプレートが活躍します

アバター
usao
記事: 1569
登録日時: 6年前

Re: 実行時型情報を使って良いのか?

#7

投稿記事 by usao » 6年前

>そんな感じであっていると思います。
ご確認いただきありがとうございます.

しかし,この方法だと例えば「要素がN種類あるうちの7種類をサポートするVisitor」
とか書くのが大変そうに思うのだけど,何か良い実装方法があるのかなぁ?

コード:

//多重継承ってほとんど書いたことないけど
//こんなにたくさん継承しなきゃならないとしたら,何か間違っているような不安な気分になってしまう
class ConcreteVisitor_A_B_D_F_X_Y_Z
     : public Visitor_T<A>, public Visitor_T<B>, ... , public Visitor_T<Z> //ここが長い...
{
    ...
};

閉鎖

“C言語何でも質問掲示板” へ戻る