7

Shapeの基本タイプへのポインターがいくつかあります。==演算子を使用してこれらのオブジェクトを比較したいと思います。オブジェクトが異なる派生型である場合、==演算子は明らかにfalseを返す必要があります。ただし、それらが同じ派生型である場合は、派生型のメンバーを比較する必要があります。

C ++ RTTIの使用は悪い習慣であり、まれで本質的な状況でのみ使用する必要があることを読みました。私が見る限り、この問題はRTTIを使用せずに一般的に解決することはできません。オーバーロードされた各==演算子は、typeidをチェックする必要があり、それらが同じである場合は、dynamic_castを実行してメンバーを比較します。これは一般的なニーズのようです。この問題にはある種のイディオムがありますか?

#include <iostream>
using namespace std;

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

    virtual bool operator == (const Shape &Other) const = 0;
};

class Circle : public Shape {
  public:
    Circle() {}
    virtual ~Circle() {}
    virtual void draw() { cout << "Circle"; }

    virtual bool operator == (const Shape &Other) const {
      // If Shape is a Circle then compare radii
    }

  private:
    int radius;
};

class Rectangle : public Shape {
  public:
    Rectangle() {}
    virtual ~Rectangle() {}
    virtual void draw() { cout << "Rectangle"; }

    virtual bool operator == (const Shape &Other) const {
      // If Shape is a Rectangle then compare width and height
    }

  private:
    int width;
    int height;
};

int main() {
  Circle circle;
  Rectangle rectangle;

  Shape *Shape1 = &circle;
  Shape *Shape2 = &rectangle;

  (*Shape1) == (*Shape2); // Calls Circle ==
  (*Shape2) == (*Shape1); // Calls Rectangle ==
}
4

4 に答える 4

10

RTTIを使用します。を使用しますが、ではなくをtypeid使用します。static_castdynamic_cast

設計の観点からは、これがまさにRTTIの目的であり、代替ソリューションは必然的に醜いものになるでしょう。

virtual bool operator == (const Shape &Other) const {
    if(typeid(Other) == typeid(*this))
    {
        const Circle& other = static_cast<const Circle&>(Other);
        // ...
    }
    else
        return false;
}

パフォーマンスの観点から: typeid安価になる傾向があり、仮想テーブルに格納されているポインタの単純なルックアップ。動的タイプを安価に比較して同等性を確認できます。

次に、適切なタイプであることがわかったら、を安全に使用できますstatic_cast

dynamic_castクラス階層を分析して継承(および複数継承も)。ここでそれに対処する必要はありません。

于 2012-07-05T10:34:57.020 に答える
4

もちろん、使用せずtypeidにキャストすることもできます。しかし、それは少し面倒なので、実行する価値があるかどうかを判断する必要があります。

バージョン 1 - ダブル ビジター

訪問者パターンを使用する

class ShapeVisitor
{
public:
    virtual void visitCircle(Circle const &) = 0;
    virtual void visitRectangle(Rectangle const &) = 0;
    // other shapes
}

クラスShape追加へ

virtual void acceptVisitor(ShapeVisitor &) = 0;

そして訪問者

class CircleComparingVisitor : public ShapeVisitor
{
    Circle const & lhs; // shorthand for left hand side
    bool equal; // result of comparison
public:
    CircleComparingVisitor(Circle const & circle):lhs(circle), equal(false){}
    virtual void visitCircle(Circle const & rhs) {equal = lhs.radius == rhs.radius;}
    virtual void visitRectangle(Rectangle const &) {}
    // other shapes
    bool isEqual() const {return equal;}
}
// other shapes analogically

class ShapeComparingVisitor
{
    Shape const & rhs; // right hand side
    bool equal;
public:
    ShapeComparingVisitor(Shape const & rhs):rhs(rhs), equal(false) {}

    bool isEqual() const {return equal;}

    virtual void visitCircle(Circle const & lhs)
    {
        CircleComparingVisitor visitor(lhs);
        rhs.accept(visitor);
        equal = visitor.isEqual();
    }
    virtual void visitRectangle(Rectangle const & lhs)
    {
        RectangleComparingVisitor visitor(lhs);
        rhs.accept(visitor);
        equal = visitor.isEqual();
    }
}

最後operator==に、仮想である必要はありません

bool Shape::operator==(const Shape &rhs) const
{
    ShapeComparingVisitor visitor(rhs);
    this->accept(visitor);
    return visitor->isEqual();
}

再考-operator==仮想であり、適切な比較ビジターを使用する可能性があります-だから、あなたは取り除くことができますShapeComparingVisitor

バージョン 2 - 二重ディスパッチ

に追加しますShape

virtual bool compareToCircle(Circle const &) const == 0;
virtual bool compareToRectangle(Rectangle const &) const == 0;

そして特定の形で実装する

たとえば

bool Circle::operator==(Shape const & rhs) const
{
    return rhs.compareToCircle(*this);
}
于 2012-07-05T10:14:56.850 に答える
1

これがまさにRTTIの目的です。コンパイル時にわかっているのは、それがであるというShape&ことだけです。したがって、意味のある比較を行う前に、実行時チェックを実行して、実際に派生型が何であるかを確認する必要があります。私は、ポリモーフィズムに違反せずにそれを行う他の方法を知りません。

さまざまな派生型の組み合わせに対して多くの無料関数を定義できますoperator ==が、おそらくポインターを介してこれらを処理しているため、ポリモーフィックな動作はありません。Shape&そのため、呼び出し元のコードでさえ、オブジェクトの型が実際にはわかりません。

したがって、ここではRTTIは(ほぼ)避けられません。実際、この種のシナリオがまさにRTTIが存在する理由です。それは特定の脆弱性を追加するため、場合によっては悪い習慣と見なされるだけです(誰もがやって来て新しいサブクラスを作成する可能性があるため、対処方法を知っているタイプでない場合は必ず処理する必要がありますShape)、そしてそれは実行時のコストを追加します。ただし、仮想メソッドを使用して、実行時のコストをすでに支払っています。

おそらく、渡されたオブジェクトに対してさらに仮想メソッド呼び出しを行っoperator ==て正しい種類の比較動作を取得するシステムを作成できるため、「ほぼ避けられない」と言いますが、実際には、別の仮想メソッドルックアップ(仮想メソッドにもコンパイラはどの実装が呼び出されるかわからないため、実行時のパフォーマンスが低下するため、具体的な関数アドレスを入力できません)は、おそらくRTTIのコストよりも速くはありません。

誰かがその費用なしでそれをする方法を知っているなら、私はそれを見たいです。

于 2012-07-04T15:51:14.207 に答える
1

オブジェクトの内部表現を掘り下げているので、ここで起こっているリスコフ置換原則の根本的な違反があると私は感じています。ただし、オブジェクトの内部表現を公開することに満足している場合 (または他の理由で公開する必要がある場合) は、このようなものが機能します。

class Shape
{
   virtual void std::string serialize() const =0;
   bool operator==( const Shape & s )
   {
      return this.serialize() == s.serialize();
   }
};
于 2012-08-24T04:17:31.720 に答える