运算符 == C++ 中派生类的重载

问题描述

我正在编写一个具有不同形状类的程序

一个类似于以下的基本形状类:

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;
    };

然后是一些类似于下面的派生类:

class Circle : public Shape
    {
    public:
        Circle(int x,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,colorRef) {}
        ~Square() {}
    };

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

我想重载形状类中的 == 运算符,以便我可以确定 2 个形状是否相同。如果我可以假设被比较的两个形状属于同一个类,那么我知道这将是相当简单的,但是我如何去测试不同派生类的 2 个对象是否相等?例如,我如何确定 Triangle t != Circle c

解决方法

您必须根据两个对象的类型确定要调用的函数。这种模式在 C++ 中称为 double-dispatch(或访问者模式)。

最常见的实现假设所有派生类(示例中的形状)都是已知的 - 因此您可以在基类中列出它们:

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
     }

};

正如你所看到的 - 要执行操作 - 你必须调用 2 个虚函数(所以双重调度)

,

好的,这里有一个想法,它使用奇怪的重复模板模式来简化派生类的实现,同时允许 == 运算符按预期工作。这可能有点矫枉过正,但它应该适用于您的场景。

首先填写您的基础 Shape 类。添加到您的基本定义的是 operator== 的实现,它调用一个名为 CompareTypesAndDimensions 的帮助程序。该函数调用两个虚拟方法,TypeCompareCompare

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;
};

上面的想法是,Circle、Triangle 和 Square 可以实现他们自己的 TypeCompareCompare 版本并完成它。 但是等等!如果我们可以通过让模板基类为我们做一些工作来节省一些输入,特别是验证两个比较实例是否属于相同类型,那会怎么样。并且不必为更简单的类型(如方形和三角形)使用库存比较函数。

我们来介绍一个继承自Shape的模板类。此类 ShapeComparable 提供了 CompareTypeCompare 的实现。它唯一需要它下面的具体类来处理的是一个方法来处理比较它自己的方法。

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

    ShapeComparable(int x,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;
    }
};

上面的神奇之处在于 TypeCompare 使用 dynamic_cast 来验证被比较的两个实例是否属于相同类型。如果您尝试将 TriangleCircle 进行比较,则动态转换会失败。因此,operator== 将返回 false。

现在让我们看看其余的类是什么样的。从 Circle 开始,它继承自 ShapeComparable 并提供 CompareDerived 的实现。

class Circle : public ShapeComparable<Circle>
{
public:
    Circle(int x,COLORREF colorRef) : ShapeComparable(x,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;
};

但是三角形和正方形很简单。

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

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

如果您需要向 Triangle 和 Square 引入新属性,您只需提供一个 CompareDerived 方法。

以上假设是您不会有从另一个具体形状类派生的其他形状。否则,将正方形与菱形进行比较时,CompareType 函数将不会互惠。