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

Difference between using non-virtual base class functions versus derived class non-implemented virtual functions

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

这里有一些背景知识。假设我有一个基础 class A 和两个派生的 classes 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

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

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

这让我想到了我的问题,使用上面的例子。假设我有一个辅助函数,派生的 classes 需要它来实现它们。

辅助函数是非虚拟成员函数(如 xorp())与辅助函数是派生的虚拟函数之间有区别吗 classes不要覆盖 (bar())

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

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

您的示例中的缺陷是您没有使用多态性。您直接对所有对象进行操作。您不会注意到任何与覆盖相关的事情,因为 none 的调用 需要 动态解析。而如果调用不是动态解析的话,虚函数和非虚函数是绝对没有区别的。要查看差异,请使用辅助免费功能:

void baz(A& a) {
  a.foo();
  a.bar();
  a.xorp();
}

int main() {
  // As before
  baz(a);
  baz(b);
  baz(c);
}

现在您应该能够看到对 foobarbaz 的调用的解析方式存在明显差异。特别是...

If I were to implement a xorp() in B, for example, it would effectively hide the A::xorp() and a call to b.xorp() would actually be a call to b.B::xorp().

...在 baz.

中将不再为真

Is there a difference between having the helper function be a non-virtual member function (like xorp()), versus the helper function being a virtual function that the derived classes do not override (bar())?

如果您将一个方法标记为虚拟但从未重写它,那么它的行为将等同于您从未将其标记为虚拟。与它在对象中调用的其他方法的关系不受影响。

这并不是说还没有"differences"。

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

此外,编译器无法始终知道您是否要使用虚拟覆盖。因此它无法优化虚拟分派的额外代码——即使您不使用它 "taking advantage"。 (有时它可以,比如如果你有一个 class 你永远不会从它派生也不会导出它......虚拟可能会被丢弃。)

对于导出的 classes,您希望其他人使用:仅仅因为 从未覆盖方法并不意味着其他人不会。除非你使用 final 并防止派生你的对象 (这被认为不是非常友好,除非你有充分的理由)。所以如果你做一些虚拟的东西,能力就在那里,一旦别人添加了一个覆盖然后是的——那时候会有不同。