C++ 类:虚拟和覆盖,还是两者都不?
C++ classes: virtual and override, or neither?
是否真的需要使用或者virtual
或者override
?
我知道有很多关于这个一般主题的问题,例如:
- C++ “virtual” keyword for functions in derived classes. Is it necessary?
- Is the 'override' keyword just a check for a overridden virtual method?
从这些以及其他标记为重复的内容(很多 "duplicates" 的答案包含至少对我来说是新的不同信息),我学到了一些东西(而且,我认为,大致为什么它们是真的):没有虚拟的覆盖将不会编译。没有覆盖的虚拟将编译,但如果你犯了一个错误并且你的方法签名不正确,编译器不会告诉你。
但是如果我省略 both 会怎样?示例:
struct BaseClass {
int a_number() {
return 1;
}
};
struct DerivedClass : BaseClass {
int a_number() {
return 2;
}
};
这会编译,并且 a_number
returns 无论我实例化 BaseClass
还是 DerivedClass
都会得到适当的结果。所以它 表现得好像 我已经覆盖了该函数。上面的代码是错误的有什么原因吗?是向后兼容性问题还是其他问题?
如果在我错过的相关问题之一中直接回答了这个问题,我深表歉意,谢谢。
编辑:Whosebug 一直将我指向 C++ “virtual” keyword for functions in derived classes. Is it necessary? 问题,正如 Wyck 在下面所做的那样。我不认为它解决了我的问题,因为我在评论中给出了 him/her,因为它们是暂时的,我将在这里重复:
"I'm specifically asking about virtual in superclass methods, while that post seems to be about virtual in subclasses and how it propagates. The accepted answer below answers my question, but I don't think your link does (it might answer the question for someone more experienced in C/C++, but I don't think it answers it for a novice coming from Python and Java, like me)."
重点:我觉得问题是相关的,但又不一样。
我接受了 selbie 的回答,因为它是第一个完整 "Answer" 回答了我的问题。 Wyck 的回答提供了很多有用的、更一般的信息。
如您声明的那样(无虚拟方法),如果未声明 [=14=] virtual
,DerivedClass
的实例在转换回 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
中以相同方式声明,编译器将发出错误.它对于捕获打字错误很有用。
静态多态(调用的方法可以在编译时确定)不需要virtual
关键字。而动态多态(其中要调用的方法必须根据对象实例的具体类型在运行时确定有问题) 是否需要 virtual
关键字。
这是一种考虑是否需要动态多态性的方法:猫发出什么声音:喵!狗发出什么声音:Woof!动物发出什么声音?你不知道,因为 "Animal" 不够具体,无法知道它发出什么声音。在这种情况下你需要动态多态性。
如果没有虚拟方法,你就说你知道如何实现关于基础的方法 class。在您的情况下,您提供了 a_number
的非虚拟实现,它适用于任何 BaseClass
。你说的是 1
。这意味着在您不知道派生的 class 是什么类型的情况下,现在可以回答 "what is your a_number" 的问题。 (我怀疑您会惊讶地发现是这种情况。)只有当您碰巧知道您正在处理派生类型时,您才会最终调用派生类型的 a_number
实现。换句话说,编译器必须有某种方式知道调用 DerivedClass::a_number。更自然的事情(可能会满足您的期望)是拥有一个虚拟方法,以便使用派生的 class 的实现而不是基础 class 的实现,即使被视为基础class 对象。
如何将对象视为基本 class 类型但仍会在 运行 时调用派生 class 的实现的神奇之处在于对象具有vtable,你可以去了解一下
非虚拟(静态多态)方法对于动物声音的情况来说是愚蠢的。当你让动物发出 Speak
时,它应该是一个虚函数,因为我们期望派生的 classes 提供不同的实现,但我们希望能够让任何动物发出声音。
但有些情况下静态多态就足够了,不需要virtual
关键字。但在派生的 classes 中行为不会改变的情况总是如此。
是否真的需要使用或者virtual
或者override
?
我知道有很多关于这个一般主题的问题,例如:
- C++ “virtual” keyword for functions in derived classes. Is it necessary?
- Is the 'override' keyword just a check for a overridden virtual method?
从这些以及其他标记为重复的内容(很多 "duplicates" 的答案包含至少对我来说是新的不同信息),我学到了一些东西(而且,我认为,大致为什么它们是真的):没有虚拟的覆盖将不会编译。没有覆盖的虚拟将编译,但如果你犯了一个错误并且你的方法签名不正确,编译器不会告诉你。
但是如果我省略 both 会怎样?示例:
struct BaseClass {
int a_number() {
return 1;
}
};
struct DerivedClass : BaseClass {
int a_number() {
return 2;
}
};
这会编译,并且 a_number
returns 无论我实例化 BaseClass
还是 DerivedClass
都会得到适当的结果。所以它 表现得好像 我已经覆盖了该函数。上面的代码是错误的有什么原因吗?是向后兼容性问题还是其他问题?
如果在我错过的相关问题之一中直接回答了这个问题,我深表歉意,谢谢。
编辑:Whosebug 一直将我指向 C++ “virtual” keyword for functions in derived classes. Is it necessary? 问题,正如 Wyck 在下面所做的那样。我不认为它解决了我的问题,因为我在评论中给出了 him/her,因为它们是暂时的,我将在这里重复: "I'm specifically asking about virtual in superclass methods, while that post seems to be about virtual in subclasses and how it propagates. The accepted answer below answers my question, but I don't think your link does (it might answer the question for someone more experienced in C/C++, but I don't think it answers it for a novice coming from Python and Java, like me)."
重点:我觉得问题是相关的,但又不一样。
我接受了 selbie 的回答,因为它是第一个完整 "Answer" 回答了我的问题。 Wyck 的回答提供了很多有用的、更一般的信息。
如您声明的那样(无虚拟方法),如果未声明 [=14=] virtual
,DerivedClass
的实例在转换回 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
中以相同方式声明,编译器将发出错误.它对于捕获打字错误很有用。
静态多态(调用的方法可以在编译时确定)不需要virtual
关键字。而动态多态(其中要调用的方法必须根据对象实例的具体类型在运行时确定有问题) 是否需要 virtual
关键字。
这是一种考虑是否需要动态多态性的方法:猫发出什么声音:喵!狗发出什么声音:Woof!动物发出什么声音?你不知道,因为 "Animal" 不够具体,无法知道它发出什么声音。在这种情况下你需要动态多态性。
如果没有虚拟方法,你就说你知道如何实现关于基础的方法 class。在您的情况下,您提供了 a_number
的非虚拟实现,它适用于任何 BaseClass
。你说的是 1
。这意味着在您不知道派生的 class 是什么类型的情况下,现在可以回答 "what is your a_number" 的问题。 (我怀疑您会惊讶地发现是这种情况。)只有当您碰巧知道您正在处理派生类型时,您才会最终调用派生类型的 a_number
实现。换句话说,编译器必须有某种方式知道调用 DerivedClass::a_number。更自然的事情(可能会满足您的期望)是拥有一个虚拟方法,以便使用派生的 class 的实现而不是基础 class 的实现,即使被视为基础class 对象。
如何将对象视为基本 class 类型但仍会在 运行 时调用派生 class 的实现的神奇之处在于对象具有vtable,你可以去了解一下
非虚拟(静态多态)方法对于动物声音的情况来说是愚蠢的。当你让动物发出 Speak
时,它应该是一个虚函数,因为我们期望派生的 classes 提供不同的实现,但我们希望能够让任何动物发出声音。
但有些情况下静态多态就足够了,不需要virtual
关键字。但在派生的 classes 中行为不会改变的情况总是如此。