为什么在派生类中覆盖方法时'virtual'关键字是可选的?

16

当在一个类中声明一个方法为virtual时,在派生类中覆盖该方法时,其被自动视为virtual。在这种情况下,C++语言使得virtual关键字变成可选的:

class Base {
    virtual void f();
};
class Derived : public Base {
    void f(); // 'virtual' is optional but implied.
};
我的问题是:为什么让virtual成为可选的呢?
我知道编译器不一定要被告知这个,但我认为如果编译器强制实施这种约束,开发人员会受益。
例如,有时候当我阅读别人的代码时,我会想知道一个方法是否是虚拟的,我必须跟踪它的超类来确定。而且一些编码标准(如Google)要求在所有子类中都必须加上virtual关键字。
5个回答

14

是的,在这种情况下,让编译器强制实施虚拟机会更好,我同意这是一种为了向后兼容而维护的设计错误。

然而,有一种技巧是没有它就不可能实现:

class NonVirtualBase {
  void func() {};
};

class VirtualBase {
  virtual void func() = 0;
};

template<typename VirtualChoice>
class CompileTimeVirtualityChoice : public VirtualChoice {
  void func() {}
};

通过上述方式,我们在编译时可以选择是否需要函数的虚拟性:

CompileTimeVirtualityChoice<VirtualBase> -- func is virtual
CompileTimeVirtualityChoice<NonVirtualBase> -- func is not virtual

...但是同意,这只是在寻找函数的虚拟性方面的小小好处,而我自己总是尽可能地在适用的情况下打上virtual。


你觉得从NonVirtualBase中删除= 0会更好 :) - Matthieu M.

8
作为相关说明,在C++0x中,您可以通过新的属性语法强制使用显式覆盖。
struct Base {
  virtual void Virtual();
  void NonVirtual();
};

struct Derived [[base_check]] : Base {
  //void Virtual(); //Error; didn't specify that you were overriding
  void Virtual [[override]](); //Not an error
  //void NonVirtual [[override]](); //Error; not virtual in Base
  //virtual void SomeRandomFunction [[override]](); //Error, doesn't exist in Base
};

您还可以通过[[hiding]]属性指定隐藏成员的时间。这会使您的代码有些冗长,但它可以在编译时捕获许多烦人的错误,例如如果您意外地输入了void Vritual()而不是void Virtual(),那么就会引入一个全新的函数,而不是覆盖现有的函数。


3

设计上的弱点,我同意。 我也认为如果有两种不同的语法表示以下两种不同的内容就好了:

  1. 声明虚函数。即在派生类中可能被重写的函数。(这实际上会在vtable中添加一个新的函数项。)
  2. 在派生类中重写虚函数。

按照当前C++规则,当重写一个函数时,很容易搞砸。如果你打错了函数名(或在参数列表中犯了错误),那么你实际上做的是(1)而不是(2)。

并且你没有错误/警告。只有在运行时才会感到惊讶。


1
同意,我认为这源于Bjarne Stroustrup和委员会希望拥有尽可能少的关键字...从而重复使用相同的关键字来表示不同的事物。同样地,我真的不喜欢在发生变量屏蔽时没有警告 :/ - Matthieu M.

2

由于语言无法强制执行“良好”风格,因此C ++通常甚至不会尝试。至少在我看来,包括冗余的说明符是否是良好的风格都值得商榷(就个人而言,当它们存在时,我讨厌它们)。

谷歌的编码标准(至少部分)在某些情况下可能是有道理的,但就C++而言,通常被认为只是平庸的建议。在某种程度上,他们甚至承认这一点-他们公开声明其中一些只是为了适应他们的旧代码。他们没有直接承认其他部分,而且(老实说)这个论点也无法支持他们的某些标准(即,有些似乎缺乏真正的理由)。


并不是说这种语言“不能”,而是它违背了C++的哲学。编译器可以检测到各种风格违规,包括在派生类中是否重用了“virtual”。 - Michael Aaron Safyan
@Michael:也许我表达得不太好,但我的意图是说它不能在一般情况下强制执行良好的风格,因此它经常不会尝试,即使在明显可以(强制执行某些人认为是良好风格的东西)的情况下也是如此。 - Jerry Coffin

0

这是一个很好的问题,我完全同意如果在基类中已经声明了虚函数,在派生类中重新声明虚函数是很好的风格。虽然有一些语言将风格融入到了语言中(例如Google Go和在某种程度上Python),但C++并不是那些语言之一。虽然编译器确实可以检测到派生类没有重用关键字“virtual”来表示在基类中已声明为“virtual”的函数(或者更重要的是,派生类声明了与基类相同名称的函数,并且在基类中未被声明为虚函数),但事实上许多编译器的设置中都存在警告(甚至错误消息)的问题。然而,在目前阶段,将这样的要求引入到语言中并不切实际,因为存在太多代码不是那么严格。此外,开发人员总是可以选择比语言更严格的标准,并且可以提高编译器警告级别。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接