使用非虚拟基类函数与派生类未实现虚拟函数之间的区别

时间:2018-11-11 12:13:33

标签: c++ override virtual-functions name-hiding

这个问题与What are the differences between overriding virtual functions and hiding non-virtual functions?略有关系,但我不是在询问技术细节,而是在询问非虚函数和虚函数的用法。

这里有一点背景。假设我有一个基类A和两个派生类B和C

#include <iostream>

class A {
public:
  A() {};
  virtual void foo() { std::cout << "foo() called in A\n"; };
  virtual void bar() { std::cout << "bar() called from A\n"; };
  void xorp() { std::cout << "xorp() called from A\n"; };
  virtual ~A() {};
};

class B : public A {
public:
  B() {};
  virtual ~B() {};
  virtual void foo() override { std::cout << "foo() called in B\n"; };
  //virtual void bar() override not implemented in B, using A::bar();
};

class C : public A {
public:
  C() {};
  virtual ~C() {};
  virtual void foo() override { std::cout << "foo() called in C\n"; };
  //virtual bar() override not implemented in C, using A::bar();
};

int main() {
  A a{};
  B b{};
  C c{};

  a.foo(); //calls A::foo()
  a.bar(); //calls A::bar()
  a.xorp(); //calls non-virtual A::xorp()

  b.foo(); //calls virtual overridden function B::foo()
  b.bar(); //calls virtual non-overridden function A::bar()
  b.xorp(); //calls non-virtual A::xorp()

  c.foo(); //calls virtual overridden function C::foo()
  c.bar(); //calls virtual non-overridden function A::bar()
  c.xorp(); //calls non-virtual A::xorp()

  return 0;
}

这将按预期输出以下内容:

foo() called in A
bar() called from A
xorp() called from A
foo() called in B
bar() called from A
xorp() called from A
foo() called in C
bar() called from A
xorp() called from A

如果我在派生类中未实现虚函数bar(),则派生类B和C中对bar()的任何调用都会解析为A :: bar()。 xorp()是一个非虚拟函数,也可以从派生类中作为b.xorp()或b.A :: xorp()进行调用。

例如,如果我要在B中实现xorp(),它将有效地隐藏A :: xorp(),而对b.xorp()的调用实际上就是对bB :: xorp()的调用

使用上面的示例,这使我想到了我的问题。假设我有一个辅助函数,派生类需要实现该函数。

使帮助程序函数成为非虚拟成员函数(例如xorp())与使帮助程序函数成为派生类不重写的虚拟函数(bar())之间有区别吗? / strong>?

通读有关类对象布局和VTABLE的演示文稿(https://www.cs.bgu.ac.il/~asharf/SPL/Inheritance.pptx,幻灯片28-35),我无法真正发现差异,因为非虚函数和非重写虚函数均指向同一位置(即基类中的函数)

谁能给我一个例子,说明这两种方法会产生不同的结果,或者是否有我未发现的警告?

2 个答案:

答案 0 :(得分:3)

您的示例中的缺陷在于您没有使用多态。您可以直接对所有对象进行操作。您不会注意到与覆盖相关的任何内容,因为所有调用 都无需动态解决。如果调用不是动态解决的,则虚拟函数和非虚拟函数之间绝对没有区别。要查看区别,请使用无辅助功能:

youtube

现在,您应该能够在解决对void baz(A& a) { a.foo(); a.bar(); a.xorp(); } int main() { // As before baz(a); baz(b); baz(c); } foobar的调用方面看到明显的不同。特别是...

  

例如,如果我要在B中实现xorp(),它将有效地隐藏A :: xorp(),而对b.xorp()的调用实际上就是对bB :: xorp()的调用

...在baz中将不再为真。

答案 1 :(得分:0)

  

将辅助函数作为非虚拟成员函数(例如xorp())与将辅助函数作为派生类不重写的虚拟函数(bar())之间有区别吗?

如果您将方法标记为虚拟但从未覆盖,则其行为等同于您从未将其标记为虚拟。与对象中调用的其他方法的关系不受影响。

这并不是说仍然没有“差异”。

对于阅读代码的人来说,它的意图肯定有所不同。如果您的xorp()方法不是虚拟的,而是依靠虚拟方法来实现其行为,那么人们将把“ xorpiness”理解为具有某些固定属性。他们可能会尝试避免在任何派生类中重新定义xorp(),并且知道只能通过定义它所依赖的虚拟方法来间接影响xorpiness。

此外,编译器并不总是知道您是否要使用虚拟替代。因此,即使您没有“利用”它,也无法优化用于虚拟调度的额外代码。 (有时候,它可以,例如,如果您有一个从不派生而又不导出的类,则可能会删除该虚拟类。)

对于导出的类,您希望其他人使用:仅仅因为 you 从未覆盖某个方法并不意味着其他人不会这样做。除非您使用final并防止派生对象(除非您有充分的理由,否则这样做并不十分友好)。因此,如果您将某项虚拟化,那么功能就存在了,一旦其他人添加了替代项,那么就可以了–那会有所不同。