【问题标题】:C++ classes: virtual and override, or neither?C++ 类:虚拟和覆盖,或者两者都不是?
【发布时间】:2020-04-17 20:30:32
【问题描述】:

真的有必要使用 either virtual override吗?

我知道关于这个一般性主题有很多问题,例如:

从这些以及其他标记为重复的内容(许多“重复”的答案包含至少对我来说是新的不同信息),我学到了一些东西(而且,我认为,大致为什么他们'是真的):没有虚拟的覆盖不会编译。没有覆盖的虚拟会编译,但如果你犯了一个错误并且你的方法签名不正确,编译器不会告诉你。

但是如果我省略 both 会发生什么?示例:

struct BaseClass {
    int a_number() {
        return 1;
    }
};
struct DerivedClass : BaseClass {
    int a_number() {
        return 2;
    }
};

这会编译,无论我实例化BaseClass 还是DerivedClassa_number 都会返回适当的结果。所以它表现得好像我已经覆盖了这个函数。上面的代码有什么错误的原因吗?是向后兼容性问题,还是其他原因?

如果我在这里错过的相关问题之一直接回答了这个问题,我深表歉意,谢谢。

编辑:StackOverflow 一直将我指向C++ “virtual” keyword for functions in derived classes. Is it necessary? 问题,正如 Wyck 在下面所做的那样。我不认为它解决了我的问题,因为我在 cmets 中给了他/她,因为它们是暂时的,我将在这里重复: “我专门询问超类方法中的虚拟,而该帖子似乎是关于子类中的虚拟及其传播方式。下面接受的答案回答了我的问题,但我认为您的链接没有(它可能会回答这个问题适合在 C/C++ 方面更有经验的人,但我认为它无法回答像我这样来自 Python 和 Java 的新手)。”

重点:我认为这些问题是相关的,但并不相同。

我接受了 selbie 的回答,因为它是第一个回答我问题的成熟“答案”。 Wyck 的回答提供了很多有用的、更一般的信息。

【问题讨论】:

  • 您得到了“正确答案”,因为编译器依赖于对象的静态类型。如果您有一个指向基类的指针,并且您尝试在属于派生类的对象上调用该函数,您将获得基类结果(要获得正确的结果,您需要虚拟)。 See here
  • @Wyck 也许我没有仔细阅读它,但我不这么认为。我特别询问超类方法中的virtual,而该帖子似乎是关于子类中的virtual 及其传播方式。下面接受的答案回答了我的问题,但我不认为您的链接可以回答(它可能会回答在 C/C++ 方面更有经验的人的问题,但我认为它不能回答来自 Python 和 Java 的新手,像我一样)。

标签: c++ class overriding virtual


【解决方案1】:

正如您声明的那样(没有虚拟方法),如果没有将 BaseClass::a_number 声明为 virtualDerivedClass 的实例在转换为 BaseClass 时将不会调用 DerivedClass 中的实现

例子:

BaseClass* instance1 = new DerivedClass();
instance1->a_number();  // returns "1", even though the object is really an instance of Derived

如果 BaseClass 声明如下:

struct BaseClass {
    virtual int a_number() {
        return 1;
    }
};

那么下面的代码就可以正常工作了

BaseClass* instance2 = new DerivedClass();
instance2->a_number();  // returns "2", virtual method invocation

override 关键字是可选的,但建议在 DerivedClass 中使用:

struct DerivedClass : BaseClass {
    int a_number() override {
        return 2;
    }
};

正如您已经观察到的,override 不会改变程序的行为,但如果 a_number 没有在 BaseClass 中以相同方式声明,编译器将发出错误。这对于捕捉拼写错误很有用。

【讨论】:

    【解决方案2】:

    静态多态(可以在编译时确定要调用的方法)不需要virtual 关键字。而动态多态性(必须在运行时根据相关对象实例的具体类型确定要调用的方法)确实 需要virtual 关键字。

    这是考虑是否需要动态多态性的一种方法:猫会发出什么声音:喵!狗发出什么声音:汪!动物发出什么声音?你不知道,因为“动物”不够具体,无法知道它发出的声音。在这种情况下,您需要动态多态性。

    如果没有虚拟方法,您就是说您知道如何实现有关基类的方法。在您的情况下,您提供了a_number 的非虚拟实现,这对任何BaseClass 都有好处。你说过它是1。这意味着现在可以在您不知道派生类是什么类型的情况下回答“您的 a_number 是什么”的问题。 (我怀疑你会惊讶地发现这种情况。)只有当你碰巧知道你正在处理一个派生类型时,你才会最终调用派生类型的a_number 实现。换句话说,编译器必须知道调用 DerivedClass::a_number 的方法。更自然的事情(可能会满足您的期望)是拥有一个虚拟方法,以便使用派生类的实现而不是基类的实现,即使将其视为基类对象。

    如何将对象视为基类类型,但在运行时仍调用派生类的实现的神奇之处在于,对象具有 vtable,您可以去了解它。

    对于 Animal 声音的情况,非虚拟(静态多态性)方法会很愚蠢。当您向动物询问Speak 时,它应该是一个虚函数,因为我们希望派生类提供不同的实现,但我们希望能够要求任何动物发出声音。

    但在某些情况下,静态多态性就足够了,并且不需要 virtual 关键字。但在派生类中行为没有改变的情况总是如此。

    【讨论】:

    • 这个答案总体上很有帮助(一个例外是在最后一句话中,我花了一些阅读时间才能确定代词“它”指的是什么;如果我是对的, "it's" 可以替换为 "static polymorphism is enough is")。我还有一个问题(至少现在是这样):动态多态性似乎总是​​有效,但使用 vtables 似乎会产生一些运行时开销。在工作的地方利用静态多态性通常被认为是一种好的做法吗?
    猜你喜欢
    • 2011-03-14
    • 2018-09-14
    • 2013-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 1970-01-01
    相关资源
    最近更新 更多