2012-01-26 9 views
8

編集:私はC + +で働いています。この多態性の問題を回避する最もエレガントな方法

したがって、形状間の交差をテストするためのメソッド/関数を作成しています。私は交差点をテストするために、実際のメソッド/関数を記述するための最良の方法を決定する必要があり、今

class Shape {}; 

class Rectangle : public Shape {}; 

class Circle : public Shape {}; 

class Line : public Shape {}; 

:私は基本的にこれを持っています。しかし、すべての私の形状が形状ポインタのリストに保存されますので、私は基本的なフォームのメソッド/関数呼び出しされます。その時点で

bool intersects (Shape* a, Shape* b); 

を、私は、形状「A」のどのタイプを決定する必要がありますと 'b'があるので、私は適切に衝突を検出することができます。

形状(「」今は「この」)のいずれかを決定するであろう:私は簡単にちょうどいくつかの仮想メソッドを使用することによって、それらのいずれかを行うことができます。しかし、私はまだ 'b'のタイプを取得する必要があります。明白な解決策は、Shapeに 'id'変数を与えてそれがどの形状であるかを分類し、それらを '切り替え'、次にdynamic_castを使用することです。しかし、これはあまりエレガントではないし、これを行うためのより多くの方法が必要であるように感じる。

提案がありますか?

+7

これはダブルディスパッチによって解決される古典的な問題です。 – Mankarse

+0

私の元々の検索は二重ディスパッチで現れたことはありませんでしたが、私はそれが自分の問題をどのように解決するかを見ています。ありがとう。編集:私はそれをアップ投票するが、私は十分な評判を持っていないかどうかわからない、または私はちょうど投票ボタンを見つけることができません。 –

答えて

7

。オブジェクト指向言語、またはオブジェクト指向の概念を実装できるC++言語のように、これは通常Visitorパターンを使用して解決されます。

Visitorインターフェイス自体は、コンクリートタイプごとに1つのコールバックを一般的に定義します。

class Circle; 
class Rectangle; 
class Square; 

class Visitor { 
public: 
    virtual void visit(Circle const& c) = 0; 
    virtual void visit(Rectangle const& r) = 0; 
    virtual void visit(Square const& s) = 0; 
}; 

次に、Shape階層がこれに適合します。 2つの方法が必要です。いずれかのタイプのビジターを受け入れる方法と、もう1つは、「適切な」交差ビジターを作成する方法です。

class Visitor; 
class Intersecter; 

class Shape { 
public: 
    virtual void accept(Visitor&) const = 0; // generic 
    virtual Intersecter* intersecter() const = 0; 
}; 

intersecterは単純です:

#include "project/Visitor.hpp" 

class Intersecter: public Visitor { 
public: 
    Intersecter(): result(false) {} 
    bool result; 
}; 

例えば、サークルのためにそれを与える:

#include "project/Intersecter.hpp" 
#include "project/Shape.hpp" 

class Circle; 

class CircleIntersecter: public Intersecter { 
public: 
    explicit CircleIntersecter(Circle const& c): _left(c) {} 

    virtual void visit(Circle const& c); // left is Circle, right is Circle 
    virtual void visit(Rectangle const& r); // left is Circle, right is Rectangle 
    virtual void visit(Square const& s); // left is Circle, right is Square 

private: 
    Circle const& _left; 
}; // class CircleIntersecter 


class Circle: public Shape { 
public: 
    virtual void accept(Visitor& v) const { v.visit(*this); } 

    virtual CircleIntersecter* intersecter() const { 
    return new CircleIntersecter(*this); 
    } 
}; 

と使用方法:

#include "project/Intersecter.hpp" 
#include "project/Shape.hpp" 

bool intersects(Shape const& left, Shape const& right) { 
    boost::scope_ptr<Intersecter> intersecter(left.intersecter()); 
    right.accept(*intersecter); 
    return intersecter->result; 
}; 

他の方法が必要な場合に二重ディスパッチ機構、次にa結果をラップし、Visitorから継承し、派生クラスによってオーバーライドされたShapeに根ざす新しい「ファクトリ」メソッドが適切な操作を提供するように、別の「仲介者のような」クラスを作成する必要があります。少し長めですが、うまくいきます。

注:intersect(circle, rectangle)intersect(rectangle, circle)を除いて同じ結果が得られることは妥当です。コードを因数分解することができ、具体的な実装にはCircleIntersecter::visit代理人がいます。これにより、コードの重複が回避されます。

+0

intersecter用に定義したコンストラクタには引数がなく、後で引数で呼び出すことができます。どうして? – user1754322

+0

@ user1754322:私は電話を見つけることができませんでしたが、実際には私は実際に電話していないようです。あなたが 'boost :: scoped_ptr intersecter(left.intersecter());'で混乱していたのですが、ここで 'intersecter'という名前の変数を作成しましたか? –

+0

私は 'boost :: scoped_ptr intersecter(left.intersecter());'は '' Intersecter intersecter 'のようなものだったと思った。intersecter()); 'それじゃない? – user1754322

4

Andrei Alexandrescuはこの問題を古典的なModern C++ Designで詳しく説明しています。コンパニオンライブラリのLokiにはthe implementation for Multi-Methodsが含まれています。

更新

ロキは、ユーザーのニーズに応じて、マルチメソッドの3つの実装を提供します。いくつかは単純化のためであり、いくつかは速度のためのものであり、いくつかは低結合に適しており、いくつかは他のものより安全性が高いものもある。この本の章は40ページ近くにわたり、読者が本のコンセプトの多くに精通していることを前提としています。ブーストを使用するのが快適な場合は、Lokiがあなたの路地にいる可能性があります。私は実際にそれを受け入れる答えにそれを引き出すことはできませんが、私が知っているC++の主題の最善の説明を指摘しました。

+1

@anon_downvoterあなたの行動を正当化しないと、あまり役に立たない。 – justin

+0

おそらくコードの欠如?とにかく、マルチメソッドは役に立ちます。 @MatthieuM。 –

+0

。おそらく。なぜ私が例を提示しなかったのかを説明するために私の答えを広げました。ありがとう。 – justin

1

あなたは、例えば、各Shape

にフィールドshapeTypeを追加することができます。@Mandarseが指摘したように、これは典型的な二重派遣問題である

class Shape { 
    virtual shapetype_t getShapeType() const; 
    // ... 
} 
2

C++ランタイムポリモーフィズムは、単一のディスパッチ(基本クラスvtable)を持ちます。

あなたの問題を解決する方法はたくさんありますが、それらの言語はネイティブでサポートできる言語をもっと強くしようとしているため、「エレガント」ではありません(Alexandrescu Loki multimethodsは非常によく隠されたハックです。 "悪い事"をカプセル化しますが、それほどうまくいきません)

ここでは、N のすべての関数を書く必要があり、実際の実行時型のTWOパラメータ 「ビジターパターン」(別の仮想関数からの仮想的な結合をコールバック)、「mutimethod」技術(一般的なdspatchテーブルを使用)、仮想関数への「動的キャスト」または関数はすべて同じことをします:2つの間接指定の後に関数を呼び出します。結果のパフォーマンスがほとんど同じであるため、技術的に「より良い」と「他のもの」を定義することはできません。

しかし、それらのうちのいくつかは、コード書込みでは他のものよりもコストが高く、コード保守では他のコストがかかります。 あなたは、トレードオフが何であるかを見積もる可能性が最も高いです。どのくらい多くクラスのあなたはが今後を追加する必要があると思いますか?

関連する問題