1

I am writing a program that has different shape classes

There is a base shape class similar to the following:

class Shape
    {
    public:
        Shape(int x, int y, int size, COLORREF colorRef);
        ~Shape();
        bool operator == (const Shape&) const;
        int x() const;
        int y() const;
        int size() const;

    protected:
        int xCoord;
        int yCoord;
        int shapeSize;
        COLORREF color;
    };

And then some derived classes similar to the following:

class Circle : public Shape
    {
    public:
        Circle(int x, int y, int size, COLORREF colorRef) : Shape(x, y, size, colorRef)
        {
            this->radius = (double)shapeSize / 2;
            this->xCenter = (double)xCoord + radius;
            this->yCenter = (double)yCoord - radius;
        }
        ~Circle() {}

    private:
        double radius;
        double xCenter;
        double yCenter;
    };

class Square : public Shape
    {
    public:
        Square(int x, int y, int size, COLORREF colorRef) : Shape(x, y, size, colorRef) {}
        ~Square() {}
    };

class Triangle : public Shape
    {
    public:
        Triangle(int x, int y, int size, COLORREF colorRef) : Shape(x, y, size, colorRef) {}
        ~Triangle() {}
    };

I would like to overload the == operator in the shape class so that I can determine if 2 shapes are identical. If I could assume both shapes being compared were of the same class then I know it would be fairly straight forward, but how do I go about testing whether 2 objects of the different derived classes are equal? For example, how do I determine that Triangle t != Circle c?

  • Hint: `virtual`. – tadman Mar 31 '21 at 06:10
  • You might need to include `friend` operator functions that handle the various combinations you want to accommodate. – tadman Mar 31 '21 at 06:11
  • Side note - your classes don't need a destructor. – selbie Mar 31 '21 at 06:22
  • 1
    Maybe a `signature` virtual function. – zdf Mar 31 '21 at 06:22
  • You could add a getClassID() function to each class that returns a unique ID for each class. Then you could compare the IDs first before comparing the contents of the two objects. – CYBERCAV Mar 31 '21 at 06:22
  • I have an answer based on the CRTP pattern. But I hit a snag. Do you allow for multiple levels of inheritance? For example, will your `Square` shape inherit from `Rectangle` (or `Rectangle` inherit from `Shape`)? And if so, how do you want the comparison to proceed? Do you want `==` to return false when comparing a Square with a a Rectangle? Or do you want to compare on the base class members? This becomes much easier if you don't allow for multiple levels of Shape inheritance (i.e. Rectangle and Square both inherit from Shape, but not from each other). Let me know, so I can answer back. – selbie Mar 31 '21 at 07:50
  • @selbie Luckily there is no deep inheritance. There is only 3 derived classes: Square, Circle, and Triangle, and they all only inherit from Shape – ParkerHarrelson123 Mar 31 '21 at 13:09

2 Answers2

1

You have to determine which function to call based on type of two objects. This pattern in C++ is called double-dispatch (or Visitor pattern).

The most common implementation assumes that all derived classes (shapes in your example) are known - so you can list them in base class:

class Circle;
class Rectangle;
// all shapes here
class Shape {
public:
   virtual ~Shape() = default; // good habit is to add virtual destructor to all polymorphic classes (those with virtual methods)

   bool operator == (const Shape& other) const {
      return equalTo(other);
   }
   
   virtual bool equalTo(const Shape& other) const = 0;
   virtual bool doEqualTo(const Circle& other) const { return false; }
   virtual bool doEqualTo(const Rectangle& other) const { return false; }
   // etc.. for all other shapes

};

class Circle : public Shape {
  // ...
protected:
     virtual bool equalTo(const Shape& other) const 
     {  
         return other.doEqualTo(*this); // call doEqualTo(Circle) - first virtual dispatch
     }
     virtual bool doEqualTo(const Circle& other) const 
     {  
         return other.center == center && other.radius == radius; // second virtual dispatch
     }

};

As you can see - to perform action - you have to call 2 virtual functions (so double-dispatch)

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • Okay so I am guessing this will return false right off the bat if I were to try and compare 2 different shapes, correct? I am going to give this is a try – ParkerHarrelson123 Mar 31 '21 at 13:14
  • Yes, that is why they return false in base class. But - of course - you might think of comparing let say Polygon and Triangle that are identical in shape - you just need to implement extra doEqualTo in those classes. – PiotrNycz Mar 31 '21 at 13:18
  • So I implemented your idea into my project and I am getting an error that I don't really understand how to fix (sorry I am a student who is still new to C++). The error is happening in the `equalTo(const Shape& other) const` method. I have it implemented for my Circle class curently, but for some reason whenever I try to call `other.doEqualTo(*this)` in the method, I am getting an error saying `C++ protected function (declared at line 36 of Shapes.hpp) is not accessible through a pointer or object`. Any ideas on how to fix this? – ParkerHarrelson123 Mar 31 '21 at 13:45
  • Ok, change to public (protected --> public) at least in base class (Shape) I just forgot about this https://stackoverflow.com/questions/16785069/why-cant-a-derived-class-call-protected-member-function-in-this-code – PiotrNycz Mar 31 '21 at 22:10
  • In the original code it is not polymorphic , there are some other consequences of adding polymorphishm (e.g. the base destructor now needs to be virtual) – M.M Mar 31 '21 at 22:18
  • @M.M Yep - do not forget to add `virtual ~Shape() = default;` – PiotrNycz Mar 31 '21 at 22:23
0

Ok, here's an idea for using the curious recurring template pattern to make implementing derived classes easier while allowing the == operator to work as expected. This maybe overkill, but it should work for your scenario.

Start by filling out your base Shape class. Added to your basic definition is an implementation of operator== that invokes a helper called CompareTypesAndDimensions. The function calls into two virtual methods, TypeCompare and Compare.

class Shape
{
public:
    Shape(int x, int y, int size, COLORREF colorRef) : xCoord(x), yCoord(y), shapeSize(size), color(colorRef) {}

    virtual ~Shape() {}; // need at least one virtual member for dynamic_cast

    int x() const { return xCoord; }
    int y() const { return yCoord; }
    int size() const { return shapeSize; }
    COLORREF col() const { return color; };

    bool operator == (const Shape& other) const
    {
        return CompareTypesAndDimensions(other);
    }

    bool BaseShapeCompare(const Shape& other) const
    {
        return ((other.xCoord == xCoord) && (other.yCoord == yCoord) && (other.shapeSize == shapeSize) && (other.color == color));
    }

    virtual bool TypeCompare(const Shape& other) const = 0;
    virtual bool Compare(const Shape& other) const = 0;

    bool CompareTypesAndDimensions(const Shape& other) const
    {
        // make sure the types checks are reciprocals
        // we don't accidently compare a "Square" with a "Rectangle" if they inherit from each other
        if (TypeCompare(other))
        {
            return Compare(other);
        }
        return false;
    }

protected:
    int xCoord;
    int yCoord;
    int shapeSize;
    COLORREF color;
};

The idea being with the above is that Circle, Triangle, and Square could just implement their own version of TypeCompare and Compare and be done with it. But wait! What if we could save some typing by having a template base class do some work for us - especially for validating that both compared instances are of the same type. And not having to a stock Compare function for the simpler types such as Square and Triangle.

Let's introduce a template class that inherits from Shape. This class, ShapeComparable provides the implementations for Compare and TypeCompare. The only thing it needs the concrete class below it to deal with is a method to handle comparing its own methods.

template <typename T>
class ShapeComparable : public Shape
{
public:

    ShapeComparable(int x, int y, int size, COLORREF colorRef) : Shape(x, y,size,colorRef)
    {}

    bool TypeCompare(const Shape& other) const override
    {
        auto pOtherCastToDerived = dynamic_cast<const T*>(&other);
        return (pOtherCastToDerived != nullptr);
    }

    bool Compare(const Shape& other) const override
    {
        if (BaseShapeCompare(other))
        {
            auto pOtherCastToDerived = dynamic_cast<const T*>(&other);
            if (pOtherCastToDerived)
            {
                return this->CompareDerived(*pOtherCastToDerived);
            }
        }
        return false;
    }

    // derived classes that don't have members to compare will just inherit this member
    virtual bool CompareDerived(const T& other) const
    {
        return true;
    }
};

The magic with the above is that TypeCompare utilizes a dynamic_cast to validate if the two instances being compared are of the same type. If you try to compare a Triangle to a Circle, the dynamic cast fails. Hence, operator== will return false.

Now let's see what the rest of the classes look like. Start with Circle, it inherits from ShapeComparable and provides an implementation for CompareDerived.

class Circle : public ShapeComparable<Circle>
{
public:
    Circle(int x, int y, int size, COLORREF colorRef) : ShapeComparable(x,y,size,colorRef)
    {
        this->radius = (double)shapeSize / 2;
        this->xCenter = (double)xCoord + radius;
        this->yCenter = (double)yCoord - radius;
    }

    bool CompareDerived(const Circle& other) const
    {
        // BaseCompare has already been invoked by the time this method is invoked.
        return ((other.radius == radius) && (other.xCenter == xCenter) && (other.yCenter == yCenter));
    }

private:
    double radius;
    double xCenter;
    double yCenter;
};

But Triangle and Square are as simple as it gets.

class Triangle : public ShapeComparable<Triangle>
{
public:
    Triangle(int x, int y, int size, COLORREF colorRef) : ShapeComparable(x, y, size, colorRef) {}
};

class Square : public ShapeComparable<Square>
{
    Square(int x, int y, int size, COLORREF colorRef) : ShapeComparable(x, y, size, colorRef) {}
};

And if you ever need to introduce a new property to Triangle and Square, you just need to provide a CompareDerived method.

The above works with the assumption is that you wouldn't have additional shapes derived from another concrete shape class. Otherwise, the CompareType function won't be reciprocal when comparing a Square to a Rhombus.

selbie
  • 100,020
  • 15
  • 103
  • 173