如何在 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;
}
您在评论中提到这种形式的比较是一种强加的限制(在子类型的兄弟姐妹之间进行比较)。如果它是一个强加的限制,您需要以某种方式使用 继承 执行此操作,那么一个选择是实现基本签名并使用 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
函数一起使用来满足层次结构。但是,为了避免 c++20 生成的对称 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
并防止歧义。
这将使 Book
和 Game
具有可比性,但与 return false
相比——而两个 Book
或两个 Game
对象可以与适当的逻辑进行比较。
也就是说...
就软件架构而言,这种方法不是一个好的设计。它需要派生的 class 来查询类型是什么——通常在您需要这样做时,这表明逻辑很古怪。当涉及到相等运算符时,它也会导致对称性的复杂化——不同的派生 class 可能会选择用不同的类型奇怪地比较事物(想象一个 Media
可能比较 true
与其他不同的媒体;此时,顺序对函数调用很重要)。
一般来说,更好的方法是定义 each 在逻辑上需要进行相等比较的任何类型之间的各个相等运算符。如果您使用的是 C++20,那么使用对称相等生成很简单;但是 C++20 之前的版本有点痛苦。
如果 Book
与 Game
相当,则定义 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;
};
这是我推荐的方法,前提是媒体的形式是固定的而不是扩展的。
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;
}
您在评论中提到这种形式的比较是一种强加的限制(在子类型的兄弟姐妹之间进行比较)。如果它是一个强加的限制,您需要以某种方式使用 继承 执行此操作,那么一个选择是实现基本签名并使用 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
函数一起使用来满足层次结构。但是,为了避免 c++20 生成的对称 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
并防止歧义。
这将使 Book
和 Game
具有可比性,但与 return false
相比——而两个 Book
或两个 Game
对象可以与适当的逻辑进行比较。
也就是说...
就软件架构而言,这种方法不是一个好的设计。它需要派生的 class 来查询类型是什么——通常在您需要这样做时,这表明逻辑很古怪。当涉及到相等运算符时,它也会导致对称性的复杂化——不同的派生 class 可能会选择用不同的类型奇怪地比较事物(想象一个 Media
可能比较 true
与其他不同的媒体;此时,顺序对函数调用很重要)。
一般来说,更好的方法是定义 each 在逻辑上需要进行相等比较的任何类型之间的各个相等运算符。如果您使用的是 C++20,那么使用对称相等生成很简单;但是 C++20 之前的版本有点痛苦。
如果 Book
与 Game
相当,则定义 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;
};
这是我推荐的方法,前提是媒体的形式是固定的而不是扩展的。