如何在 C++ 中重载多态 == 和 != 运算符

How to overload polymorphic == and != operator in c++

class Media {
public:
    bool operator==(const Media& other) const {}
    bool operator!=(const Media& other) const {}
};

class Book : public Media {
public:
    bool operator==(const Book& other) const {} // commenting out this line solves this issue.
    bool operator!=(const Book& other) const {}
};

class Game : public Media {
public:
    bool operator==(const Game& other) const {}
    bool operator!=(const Game& other) const {}
};

int main() {
    Book book;
    Game game;

    bool res = book == game;  // doesn't compile.
}

我有这 3 个 class,它们 必须 定义了自己的 == 和 != 运算符。但是我还必须使用这些运算符比较两个兄弟姐妹。

我本可以编写一个(纯)虚函数,例如,virtual bool equals(const Media& other) const 在基 class 中,subclass 会覆盖它。然后在 base class Media 中的 == 和 != 运算符定义的主体中调用该函数。但是,当我在 Book class 中添加另一个 bool operator==(const Book& other) const {} 时,该功能消失了(Game class 也是如此)。

现在我想比较使用这些运算符的兄弟姐妹,并且在这 3 个 class 中仍然有全部 6 个定义。我如何让它发挥作用?

由于 std::visit/std::variant (C++17):

,你可能会进行双重分派
class Media;
class Book;
class Game;

using MediaPtrVariant = std::variant<const Media*, const Book*, const Game*>;

class Media {
public:
    virtual ~Media () = default;
    virtual MediaPtrVariant asVariant() const { return this; }
};

class Book : public Media {
public:
    MediaPtrVariant asVariant() const override { return this; }
};

class Game : public Media {
public:
    MediaPtrVariant asVariant() const override { return this; }
};

struct EqualVisitor
{
    template <typename T>
    bool operator()(const T*, const T*) const { return true; }

    template <typename T, typename U>
    bool operator()(const T*, const U*) const { return false; }
};


bool operator ==(const Media& lhs, const Media& rhs)
{
    return std::visit(EqualVisitor(), lhs.AsVariant(), rhs.AsVariant());
}

bool operator !=(const Media& lhs, const Media& rhs)
{
    return !(lhs == rhs);
}

int main()
{
    Book book;
    Game game;

    bool res = book == game;
}

Demo

您在评论中提到这种形式的比较是一种强加的限制(在子类型的兄弟姐妹之间进行比较)。如果它是一个强加的限制,您需要以某种方式使用 继承 执行此操作,那么一个选择是实现基本签名并使用 dynamic_cast。请注意,这不是 clean 方法,但如果这是某种形式的赋值,它可能是此问题的预期解决方案。

dynamic_cast 使用运行时类型信息 (RTTI) 来确定基 class 的实例是否实际上是派生 class 的实例。当您将它与指针参数一起使用时,它会在失败时 returns nullptr —— 这很容易测试:

auto p = dynamic_cast<const Book*>(&other);
if (p == nullptr) { // other is not a book
  return false;
}
// compare books

您可以将其与 virtual 函数一起使用来满足层次结构。但是,为了避免 生成的对称 operator==/operator!= 函数可能出现歧义,通常最好通过 named virtual 函数而不是 operator== 本身,以防止歧义:

class Media {
public:
  virtual ~Media() = default;

  bool operator==(const Media& other) const { return do_equals(other); }

private:
  virtual bool do_equals(const Media& other) const = 0;
};

class Book : public Media {
  ...
private:
  bool do_equals(const Media& other) const override {
    auto* p = dynamic_cast<const Book*>(&other);
    if (p == nullptr) { return false; }

    return (... some comparison logic ...);
  }
  ...
};

... Same with Game ...

因为我们从未定义 operator==(const Book&)operator==(const Game&),所以我们不会在 base-class' operator== 中看到这个影子;相反,它总是通过基地的 operator==(const Media&) 进行调度——这是非 virtual 并防止歧义。

这将使 BookGame 具有可比性,但与 return false 相比——而两个 Book 或两个 Game 对象可以与适当的逻辑进行比较。

Live Example


也就是说...

就软件架构而言,这种方法不是一个好的设计。它需要派生的 class 来查询类型是什么——通常在您需要这样做时,这表明逻辑很古怪。当涉及到相等运算符时,它也会导致对称性的复杂化——不同的派生 class 可能会选择用不同的类型奇怪地比较事物(想象一个 Media 可能比较 true 与其他不同的媒体;此时,顺序对函数调用很重要)。

一般来说,更好的方法是定义 each 在逻辑上需要进行相等比较的任何类型之间的各个相等运算符。如果您使用的是 C++20,那么使用对称相等生成很简单;但是 C++20 之前的版本有点痛苦。

如果 BookGame 相当,则定义 operator==(const Game&)operator==(const Book&, const Game&)。是的,这可能意味着您要为每个 operator== 定义大量的;但它更连贯,并且可以获得更好的对称性(尤其是 C++20 的对称相等性):

bool operator==(const Game&, const Book&);
bool operator==(const Book&, const Game&); // Generated in C++20
bool operator==(const Game&, const Game&);
bool operator==(const Book&, const Book&);

在这样的组织中,Media 甚至可能不像 'Base class' 那样合乎逻辑。考虑某种形式的静态多态性可能更合理,例如使用 std::variant —— @Jarod42 的回答中提到了这一点。这将允许类型被均匀存储和比较,但不需要从基类转换为派生类型:

// no inheritance:
class Book { ... };
class Game { ... };

struct EqualityVisitor {
  // Compare media of the same type
  template <typename T>
  bool operator()(const T& lhs, const T& rhs) const { return lhs == rhs; }

  // Don't compare different media
  template <typename T, typename U>
  bool operator()(const T&, const U&) const { return false; }
};

class Media
{
public:
  ...

  bool operator==(const Media& other) const {
    return std::visit(EqualityVisitor{}, m_media, other.m_media);
  }
private:
  std::variant<Book, Game> m_media;
};

Live Example

这是我推荐的方法,前提是媒体的形式是固定的而不是扩展的。