【问题标题】:How to override a pure virtual function using a subclass reference in the overriden function如何使用被覆盖函数中的子类引用覆盖纯虚函数
【发布时间】:2020-04-10 19:31:54
【问题描述】:

所以我在派生类中重写纯虚函数时遇到了问题。 类的实现和声明如下所示:

class Base{
private:
   size_t id;
public:
   virtual bool isEqual(const Base& src) const =0;
};

class Derived: public Base{
private:
    string str;
public:
    virtual bool isEqual(const Derived& src) const override
    {
        return (this->str == src.str);
    }
};

所以当我像这样实现它时,它会遇到编译器错误,例如

member function declared with 'override' does not override a base class member function

能否请您告诉我如何才能做到这一点,并解释一下为什么我的版本不起作用。提前致谢!

【问题讨论】:

  • 因为参数不同,您没有覆盖基类函数。要覆盖一个函数,您需要指定完全相同的签名。
  • 我知道参数是不同的,但我需要让它们不同,因为基类中不包含 string str 数据成员,编译器会在我的 isEqual 中特别抛出错误覆盖成员函数...所以我想我应该重载该函数并删除使其成为纯虚函数的=0 部分??
  • @VissarionMoutafis 错误,Derived Base。因此,您可以使用相同的签名(采用const Base &)覆盖它并传递Derived 对象将正常工作。在函数体中可以dynamic_cast。如果失败,您可以抛出异常或任何您想要的(给出错误类型,Derived 预期,...)。
  • ... 或者在给定的情况下简单地返回 false:如果类型不匹配,则对象也不能相等。
  • @VissarionMoutafis 如果dynamic_cast 失败,您返回 false。如果是这样,则您有不同的对象类型,并且它们不能相等。 否则您现在已经按照预期返回字符串比较的结果。

标签: c++ overriding abstract-class virtual-functions


【解决方案1】:

您不能以这种方式更改函数签名 - 请阅读 co- and contravariance 了解详细信息以及为什么 C++ 不允许函数 参数(允许协变返回类型)。

另一方面,如果通过对基的引用来引用另一个对象,则根本不会调用覆盖(实际上是:重载!)函数:

Derived d1;
Derived d2;
Base& b2 = d2;

d1.isEqual(b2); // how do you imagine the derived version to get called now???

解决方案的关键是dynamic_cast

struct Base
{
    virtual ~Base() { }
    virtual bool operator==(Base const& other) = 0;
};

struct Derived : Base
{
    bool operator==(Base const& other) override
    {
        auto o = dynamic_cast<Derived const*>(&other);
        return o && o->str == this->str;
    }
};

请注意,我将函数重命名为operator==;这在 C++ 中要自然得多。有了它,您可以在上面的示例中与:

bool isEqual = d1 == b2;

编辑

老师告诉我们不能重载运算符

好吧,那么只需恢复重命名...实际上,运算符与其他任何函数一样普通,只是调用语法不同(实际上:存在替代变体,您也可以始终调用 d1.operator ==(d2) )。

【讨论】:

  • “老师告诉我们不能重载操作符”——实际上最好的方法是外部函数bool operater ==(const Base&amp; rhs, const Base&amp; lhs) { return rhs.isEqual(lhs); }——当有一个从其他东西到基础或派生对象。
  • 哇,你想出了这个。我很惊讶你不能使用派生类来实现一个带有签名的虚函数。派生类是一个超集,因此它应该能够随时用作其基类。在 c++ 中,它适用于其他一切。将基类向上转换为派生类永远不会感觉不对。
  • @JoeC 准确地说:派生类的实例构成了基类实例的子集!想象一个基类Cat 和派生类LionTigerCheetah。所有派生类也是Cats,但并非所有Cats 都是Tigers。如果现在另一个基类接受Cats,但派生类只接受Tigers,则基类可以接受Lions,但派生类不接受。因此,要实现的接口仍然不完整。 返回派生类型而不是基类型(协变返回值)是类型安全的(但如果按值返回,可能会发生对象切片)。
  • @JoeC 要接受参数,我们可能会在子类中使用更通用的类型,例如。 G。在 base 中接受 Tiger,但在派生(逆变参数)中接受 Cat。但是我们可以使用函数参数作为额外的返回值(通过指针/引用)!如果现在 base 需要将 Tiger 实例的地址放入 Tiger 指针中,但派生接受 Cat 指针会放置 Cheetah 而我们又遇到了麻烦。我们现在需要一个协变参数!由于编译器无法决定(用作 in 或 out 参数或两者兼有),C++ 中根本不允许使用变体参数。
【解决方案2】:

多态的原理是DerivedBase。如果你想重写一个函数,签名必须相同。

解决问题的正确方法是将覆盖定义为:

bool Derived::isEqual(const Base & src) const
{
    try
    {
        Derived & d = dynamic_cast<Derived &>(src);
        return (this->str == d.str);
    }
    catch(const std::bad_cast & e)
    {
        // src is not a Derived
        return false;
    }
}

如果你想使用指针,你可以这样做:

bool Derived::isEqual(const Base * src) const
{
    const Derived * d = dynamic_cast<const Derived*>(src);

    if(d == nullptr) // src is not a Derived*
        return false;
    else
        return (this->str == d->str);
}

当然,它假定您在 Base 中有一个匹配的定义要覆盖。


@Aconcagua 提供的解决方案以更优雅的方式使用了相同的想法。我建议改用他的解决方案。

【讨论】:

  • 如果我们有另一个像Derived2 这样的派生类并且如果你传递Derived2 的对象,程序就会崩溃!我认为有一个肮脏的解决方案比为用户提供崩溃的可能性更好
  • @rezaebrh 它没有。你为什么这么认为?
  • 你用过吗?在所有情况下它都不会返回 false!
  • @rezaebrh 在您提供的链接中,由于缺少空指针检查而导致崩溃。这个答案中应用了这样一个,所以一切都很好。
  • @rezaebrh 这些是必须单独解决的正交问题,例如。 G。正确的线程同步。但这超出了这个问题的范围......
猜你喜欢
  • 2021-09-30
  • 2013-10-04
  • 1970-01-01
  • 2020-08-21
  • 2014-05-22
  • 2021-11-01
  • 2013-01-16
  • 2014-03-08
  • 2022-01-13
相关资源
最近更新 更多