用于派生类中的函数的C ++“虚拟”关键字。 有必要吗?

用下面给出的结构定义…

struct A { virtual void hello() = 0; }; 

方法1:

 struct B : public A { virtual void hello() { ... } }; 

方法2:

 struct B : public A { void hello() { ... } }; 

这两种方法重写hello函数有没有区别?

他们完全一样。 除了第一种方法需要更多的input并且可能更清楚之外,它们之间没有区别。

函数的“虚拟性”是隐式传播的,但是如果virtual关键字没有明确使用,至less有一个编译器会产生一个警告,所以如果仅仅为了保持编译器的安静,你可能会想使用它。

从纯粹的文体angular度来看,包括virtual关键词明确地向用户宣传该function是虚拟的。 这对任何进一步分类B的人来说都很重要,而不必检查A的定义。 对于深层次的层次结构,这变得尤为重要。

virtual关键字在派生类中不是必需的。 以下是来自C ++草案标准(N3337)(重点介绍)的支持文档:

10.3虚拟function

2如果在Base类中声明一个虚拟成员函数vf ,并且在直接或间接从Base DerivedDerived使用一个名称相同的成员函数vf ,参数types列表(8.3.5),cv-qualification,并且声明了与Base::vf相同的ref-qualifier(或缺less),则Derived::vf也是虚拟的( 不pipe它是否被声明 ),并且覆盖Base::vf

不,不需要派生类的虚拟函数覆盖上的virtual关键字。 但值得一提的是一个相关的陷阱:一个虚拟函数失效。

如果您打算重写派生类中的虚函数,但在签名中发生错误,以便声明新的和不同的虚函数,则会发生重写失败 。 这个函数可能是基类函数的一个重载 ,或者它的名字可能有所不同。 无论您是否在派生类函数声明中使用virtual关键字,编译器都无法知道您打算从基类中覆盖函数。

然而,这个缺陷是由C ++ 11 显式覆盖语言function所致谢,它允许源代码清楚地指定成员函数是用来覆盖基类函数的:

 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 }; 

编译器将发出编译时错误,编程错误将立即显而易见(也许Derived中的函数应该将float作为参数)。

请参阅WP:C ++ 11 。

添加“虚拟”关键字是一个很好的做法,因为它提高了可读性,但并不是必需的。 在基类中声明为虚拟的函数,在派生类中具有相同的签名的默认情况下被视为“虚拟”。

在编写派生类中的virtual或省略它时,编译器没有区别。

但是你需要看基类来获取这些信息。 因此,我build议在派生类中也添加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); } 

有趣的是,你现在可以定义接口和非接口函数来定义类。 这对于库之间的交互接口非常有用(不要将其作为单个库的标准devise过程)。 如果你愿意的话,你甚至可以把B定义为某种东西,这对于你所有的类都是typedef

请注意,如果你这样做,你可能想要声明复制/移动构造函数作为模板:允许从不同的接口构造允许你在不同的B<>types之间进行“转换”。

是否应该添加对const A& t_hello()支持是个疑问。 这种重写的通常原因是从基于inheritance的专业化转移到基于模板的专业化,主要是出于性能原因。 如果你继续支持旧的界面,你很难检测到(或阻止)旧的用法。

我一定会包括孩子类的虚拟关键字,因为我。 可读性。 II。 这个子类我可以进一步下来,你不希望进一步的派生类的构造函数调用这个虚函数。