8

派生クラスとを含む抽象基本クラス があると想像してください。ShapeCircleRectangle

class Shape {};
class Circle : public Shape {};
class Rectangle : public Shape {};

Shape*2つのポインターがあると仮定して、2つの形状が等しいかどうかを判断する必要があります。(これは、のインスタンスが2つありvector<Shape*>、それらが同じ形状であるかどうかを確認したいためです。)

これを行うための推奨される方法は、ダブルディスパッチです。私が思いついたのはこれです(ここでは非常に単純化されているため、形状は同じタイプの他のすべての形状と同じです):

class Shape {
public:
    virtual bool equals(Shape* other_shape) = 0;
protected:
    virtual bool is_equal(Circle& circle) { return false; };
    virtual bool is_equal(Rectangle& rect) { return false; };
    friend class Circle;    // so Rectangle::equals can access Circle::is_equal
    friend class Rectangle; // and vice versa
};

class Circle : public Shape {
public:
    virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
protected:
    virtual bool is_equal(Circle& circle) { return true; };
};

class Rectangle : public Shape {
public:
    virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
protected:
    virtual bool is_equal(Rectangle& circle) { return true; };
};

これは機能しますが、派生クラスごとに個別のequals関数とfriend宣言を追加する必要があります。次に、まったく同じ関数を各派生クラスにShapeコピーして貼り付ける必要があります。これは、たとえば10種類の形状の場合、非常に多くの定型文です。equals

それを行うためのより簡単な方法はありますか?

dynamic_cast問題外です。遅すぎる。(はい、ベンチマークしました。アプリでは速度が重要です。)

私はこれを試しましたが、機能しません:

class Shape {
public:
    virtual bool equals(Shape* other_shape) = 0;
private:
    virtual bool is_equal(Shape& circle) { return false; };
};

class Circle : public Shape {
public:
    virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
private:
    virtual bool is_equal(Circle& circle) { return true; };
};

class Rectangle : public Shape {
public:
    virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
private:
    virtual bool is_equal(Rectangle& circle) { return true; };
};

equals()同じ形状であっても、常にfalseを返します。is_equal(Shape&)「より具体的な」一致が利用可能な場合でも、ディスパッチは常に基本機能を選択しているようです。これはおそらく理にかなっていますが、理由を理解するのに十分なほどC++ディスパッチを理解していません。

4

6 に答える 6

5

ダブルディスパッチはよく研究されています。ダブルディスパッチの一般化は「マルチメソッド」と呼ばれます。

Modern C++ Designの第 11 章では、この問題について詳しく説明しています。あなたが説明したアプローチdynamic_cast<>は、セクション11.3「ダブルスイッチオンタイプ:ブルートフォース」にあります。著者は、ほとんどの作業を自動化し、対称オーバーロードを自動的に生成する方法についても説明しています。std::map<>次に、とに基づく対数ディスパッチを紹介しstd::type_infoます。最後に、セクションは「Constant-Time Multimethods: Raw Speed」で終わります。これは、(大まかに) コールバック関数のマトリックスに基づいています。

提示されたソリューションには、複数の (および仮想の) 継承が存在する場合の厄介な落とし穴を回避するために、ファンクターとキャストの処理に関する長い説明が含まれています。

C++ でマルチメソッドを実装することを検討している場合は、本を読んで提案されたソリューションを実装することを強くお勧めします。

于 2011-09-12T21:53:23.670 に答える
5

次のようなメソッドを作成する場合:

virtual bool is_equal(Shape& circle) { return false; };

そしてサブクラスでは、

virtual bool is_equal(Circle& circle) { return true; };

これらは同じ方法ではありません。2 つの別々の仮想メソッドがあり、どちらもオーバーライドされていません ( Ben Voigt が指摘したように、それらはオーバーロードされていません)。を呼び出すとShape::is_equal、バージョンは 1 つだけです: Shape::is_equal(Shape&)... これはオーバーライドされず、常に false を返します。

親クラスで個別のオーバーロードされたメソッドを定義してから、子クラスでそれらをオーバーライドする必要があります。例えば、

class Shape {
    // Choice between these two methods happens at compile time...
    virtual bool is_equal(Circle& circle) { return false; };
    virtual bool is_equal(Rectangle& circle) { return false; };
};

class Rectangle : Shape {
    // Choice between this and Shape::is_equal(Rectangle&) happens at runtime...
    virtual bool is_equal(Rectangle& circle) { return true; };
};

ただし、このようなトリックを使用すると、C プログラマーが行う方法のパフォーマンスや単純さにはおそらく近づかないでしょう。

typedef enum {
    SHAPE_CIRCLE,
    SHAPE_RECTANGLE
} shape_type_t;

struct shape {
    shape_type_t type;
};

struct circle {
    shape_type_t type;
    ...
};

struct rectangle {
    shape_type_t type;
    ...
};

bool shape_equal(struct shape *x, struct shape *y)
{
    if (x->type != y->type)
        return false;
    switch (x->type) {
    case SHAPE_CIRCLE:
        return circle_equal((struct circle *) x, (struct circle *) y);
    case SHAPE_RECTANGLE:
        ...;
    }
}

オーバーロードと仮想メソッドによってコードが C バージョンよりも複雑になっている場合は、この特定の問題をオーバーロードと仮想メソッドで解決するかどうかを再考することをお勧めします。

于 2011-09-12T20:24:46.570 に答える
1

dynamic_castが遅すぎる場合は、型列挙と静的キャストを使用できます...

enum ShapeType
{
    SHAPE_TYPE_CIRCLE,
    SHAPE_TYPE_RECTANGLE
};

struct Shape
{
    virtual ShapeType GetShapeType() const = 0;
    virtual bool isEqual(const Shape& other) const = 0;
};

struct Circle : Shape
{
    virtual ShapeType GetShapeType() const { return SHAPE_TYPE_CIRCLE; }

    virtual bool isEqual(const Shape& other) const
    {
        if (other.GetShapeType() == SHAPE_TYPE_CIRCLE)
        {
            const Circle *circle = static_cast<const Circle*>(&other);

            // do some circle specific comparison
            return true;
        }
        return false;
    }
};
于 2011-09-12T20:32:23.677 に答える
0

私の設計では、Shape::operator==メソッドをプライベートに移動し、実装しません。これを正しく解決するための作業量は、努力する価値がありません。

言い換えると、 のベクトルが与えられると、次のようになりShape *ます。

std::vector<Shape *> my_shapes;

私は次のことができます:

my_shapes.push_back(new Rectangle);
my_shapes.push_back(new Circle);

オブジェクトを比較するときに問題が発生します。

Shape * p_shape_1 = my_shapes[0];
Shape * p_shape_2 = my_shapes[1];
if (*p_shape_1 == *p_shape_2) {...}

この式は次と同等です。

p_shape_1->operator==(*p_shape_2);

仮想操作またはポリモーフィック操作が行われている場合、これは次のようになります。

Rectangle::operator==( (円));

つまり、Rectangle が Circle やその他の Shape と比較される可能性が非常に高くなります。無効な比較。

そのため、私の設計では、基本クラス ポインターに基づく等値比較を禁止しています。基本クラスへのポインターを使用して比較できる唯一のものは、基本クラスのコンテンツです。

于 2011-09-13T00:54:39.827 に答える
0

dynamic_cast仮想関数は、次のように RTTI 型チェックを簡単に置き換えることができます: http://ideone.com/l7Jr5

struct Shape
{
    struct subtype { enum { Shape, Circle, Rectangle, ColoredCircle }; };

    virtual bool is_a( int type ) const { return type == subtype::Shape; }
    virtual bool is_equal(const Shape& s) const { return false; }
};

struct Rectangle : Shape
{
    virtual bool is_a( int type ) const { return type == subtype::Rectangle || Shape::is_a(type); }
    virtual bool is_equal(const Shape& s) const
    {
        if (!s.is_a(subtype::Rectangle)) return false;
        const Rectangle& r = static_cast<const Rectangle&>(s);
        return true; // or check width and height
    }
};

struct Circle : Shape
{
    virtual bool is_a( int type ) const { return type == subtype::Circle || Shape::is_a(type); }
    virtual bool is_equal(const Shape& s) const
    {
        if (!s.is_a(subtype::Circle)) return false;
        const Circle& c = static_cast<const Circle&>(s);
        return true; // or check radius
    }
};

struct ColoredCircle : Circle
{
    virtual bool is_a( int type ) const { return type == subtype::ColoredCircle || Circle::is_a(type); }
};

int main(void)
{
    Rectangle x;
    Shape y;
    return x.is_equal(y);
}

--

「まったく同じ」関数のコピーが 10 個あるのはなぜですか? Rectangle::is_equal(const Rectangle&) constRectangle 固有のメンバーを比較するべきではありませんか?

あなたが示したコードの場合のように、すべての長方形が単一の等価クラスに分類される場合、等価クラスを返す単一の仮想関数を持つことができます。

于 2011-09-12T21:16:29.613 に答える
0

私は通常、dynamic_cast と仮想関数を参照します。コンパイラがそれほど愚かでない場合、1 つのステップを動的にキャストすることは、vtable で 2 つのジャンプを行うこととそれほど違いはありません。

class shape
{
protected:
   virtual bool is_equal(const shape* s) const=0;
   friend bool oeprator==(const shape& a, cost shape& b)
   { return a.is_equal(&b); }
};

class circle: public shape
{
    double radius;
    point<duouble> center;
protected:
    virtual bool is_equal(const shape* s) const
    {
        const circle* p = dynamic_cast<const circle*>(s);
        return p && p->radius==radius && p->center==center;
    }
};

長方形やその他の形状でも同じです。基本的に、デュアル ディスパッチには、N が被分類者の場合、N 2の機能が必要です。このように、必要な関数は N 個 (クラスごとに 1 つ) だけです。

動的キャストが遅すぎると感じた場合は、基本クラスで宣言され、派生クラスによって適切に初期化された列挙型を使用できます。ただし、これには、新しいクラスが追加されるたびに列挙値を更新する必要があります。例: class shape { protected: enum shape_type { no_shape, circle_shape,rectangle_shape }; shape_type my_type; virtual bool is_equal(const shape* s) const=0; friend bool oeprator==(const shape& a, cost shape& b) { return a.is_equal(&b); } shape() :my_type(no_shape) {} };

class circle: public shape
{
    double radius;
    point<duouble> center;
protected:
    virtual bool is_equal(const shape* s) const
    {
        const circle* p = static_cast<const circle*>(s);
        return my_type == s->my_type && p->radius==radius && p->center==center;
    }
public:
    circle() { my_type = circle_shape; }
};

base_defined 列挙型に依存することが受け入れられない場合 (可能なクラスの数が不明な場合)、次のようなトリックで一義的に型を表すことができる単純な値 (例: 整数) に依存できます。

int int_generator()
{ static int x=0; return ++x; }

template<class T>
int  id_for_type()
{ static int z = int_generator(); return z; }

class shape
{
...
int my_type;
};


class circle
{
...
   circle() { my_type = id_for_type<circle>(); }
};
于 2011-09-12T21:09:15.100 に答える