使用下面给出的结构定义...
struct A {
virtual void hello() = 0;
};
方法一:
struct B : public A {
virtual void hello() { ... }
};
方法二:
struct B : public A {
void hello() { ... }
};
这两种方式覆盖 hello 函数有什么区别吗?
使用下面给出的结构定义...
struct A {
virtual void hello() = 0;
};
方法一:
struct B : public A {
virtual void hello() { ... }
};
方法二:
struct B : public A {
void hello() { ... }
};
这两种方式覆盖 hello 函数有什么区别吗?
它们完全相同。除了第一种方法需要更多的打字并可能更清晰之外,它们没有任何区别。
函数的“虚拟性”是自动传播的,然而我使用的至少一种编译器会生成警告,如果没有显式地使用virtual
关键字,所以你可能想使用它,即使只是为了让编译器保持安静。
从纯粹的风格角度来看,包括virtual
关键字清晰地“宣传”了这个函数是虚拟的事实。对于任何进一步子类化B而不必检查A的定义的用户,这将非常重要。对于深层次的类层次结构,这尤其重要。
virtual
是不必要的。即使在深层次的类层次结构中,这一点也是非常明确的。 - undefinedvirtual
关键字在派生类中不是必需的。以下是来自C++草案标准(N3337)的支持文件(强调我的部分):virtual
关键字并非必需。但是有一个相关的陷阱值得一提:未能成功重载虚函数。virtual
关键字,编译器都无法知道你打算重载一个来自基类的函数。struct Base {
virtual void some_func(float);
};
struct Derived : Base {
virtual void some_func(int) override; // ill-formed - doesn't override a base class method
};
添加 "virtual" 关键字是一个好习惯,因为它提高了可读性,但并不是必需的。在基类中声明为虚函数,并且在派生类中具有相同签名的函数默认被认为是 "虚拟 "的。
当您在派生类中写入 virtual
或省略它时,编译器无区别。
但是,您需要查看基类以获取此信息。因此,如果您想向人类显示此函数是虚拟的,建议您在派生类中也添加 virtual
关键字。
为了使基类函数可以被覆盖,应该在其前面添加virtual
关键字。在您的示例中,struct A
是基类。对于在派生类中使用这些函数,virtual
关键字没有任何意义。但是,如果您希望您的派生类也成为一个基类,并且您希望该函数可以被覆盖,则必须在函数前面加上 virtual
。
struct B : public A {
virtual void hello() { ... }
};
struct C : public B {
void hello() { ... }
};
这里 C
继承自 B
,所以 B
不是基类(它也是一个派生类),而 C
是派生类。
继承结构图如下:
A
^
|
B
^
|
C
因此,你应该在可能有子类的潜在基类中,在函数前面加上virtual
。 virtual
允许子类覆盖你的函数。在派生类中的函数前面放置virtual
没有任何问题,但并不需要这样做。尽管如此,建议这样做,因为如果有人想要从你的派生类继承,他们会不满意方法重写不像预期那样工作。
因此,在涉及继承的所有类中,在函数前面放置virtual
,除非你确定该类不会有任何需要覆盖基类函数的子类。这是一种良好的实践。
当你有模板并开始将基类作为模板参数时,会有相当大的区别:
struct None {};
template<typename... Interfaces>
struct B : public Interfaces
{
void hello() { ... }
};
struct A {
virtual void hello() = 0;
};
template<typename... Interfaces>
void t_hello(const B<Interfaces...>& b) // different code generated for each set of interfaces (a vtable-based clever compiler might reduce this to 2); both t_hello and b.hello() might be inlined properly
{
b.hello(); // indirect, non-virtual call
}
void hello(const A& a)
{
a.hello(); // Indirect virtual call, inlining is impossible in general
}
int main()
{
B<None> b; // Ok, no vtable generated, empty base class optimization works, sizeof(b) == 1 usually
B<None>* pb = &b;
B<None>& rb = b;
b.hello(); // direct call
pb->hello(); // pb-relative non-virtual call (1 redirection)
rb->hello(); // non-virtual call (1 redirection unless optimized out)
t_hello(b); // works as expected, one redirection
// hello(b); // compile-time error
B<A> ba; // Ok, vtable generated, sizeof(b) >= sizeof(void*)
B<None>* pba = &ba;
B<None>& rba = ba;
ba.hello(); // still can be a direct call, exact type of ba is deducible
pba->hello(); // pba-relative virtual call (usually 3 redirections)
rba->hello(); // rba-relative virtual call (usually 3 redirections unless optimized out to 2)
//t_hello(b); // compile-time error (unless you add support for const A& in t_hello as well)
hello(ba);
}
我一定会在子类中包含Virtual关键字,因为