I have some pointers to the base type of Shape. I want to compare these objects using the == operator. The == operator must explicitly return false if the objects are of a different derived type. If they have the same derived type, then the members of the derived type should be compared.
I read that using C ++ RTTI is bad practice and should only be used in rare and significant circumstances. As far as I can see, this problem cannot be solved without using RTTI. Each overloaded operator == needs to check the typeid, and if they are the same, do dynamic_cast and compare the elements. This seems like a common need. Is there any idiom for this problem?
#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 == }
RTTI. typeid, static_cast, dynamic_cast.
typeid
static_cast
dynamic_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 ( , " " ), , " java" ), ( ). .
, typeid . , , .
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
ShapeComparingVisitor
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); }
, RTTI. , , , Shape&, , , , . , .
Shape&
operator == , , , -, Shape&, .
operator ==
, RTTI () , , RTTI. , ( , , , , , Shape), . , .
" ", , , - , , operator ==, , (, , , , ), , , RTTI.
- , .
I feel that there is a fundamental violation of the Liskov signature principle, as you dig into the internal representations of objects. However, if you are happy to expose the internal representation of your objects (or you must do this for other reasons), then something like this will work.
class Shape { virtual void std::string serialize() const =0; bool operator==( const Shape & s ) { return this.serialize() == s.serialize(); } };