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

C++ 使用非虚基类函数与派生类非实现虚函数之间的区别,c++,overriding,virtual-functions,name-hiding,C++,Overriding,Virtual Functions,Name Hiding,这个问题与虚拟函数有点关系,但我不是问技术细节,而是问非虚拟函数和虚拟函数的用法 这里有一点背景。假设我有一个基类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 fro

这个问题与虚拟函数有点关系,但我不是问技术细节,而是问非虚拟函数和虚拟函数的用法

这里有一点背景。假设我有一个基类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;
}
如果在派生类中没有实现虚拟函数bar(),那么在派生类B和C中对bar()的任何调用都会解析为A::bar()。xorp()是一个非虚函数,也可以从派生类中以b.xorp()或b.a::xorp()的形式调用

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

这就引出了我的问题,使用上面的例子。假设我有一个helper函数,派生类需要它来实现

让helper函数成为非虚拟成员函数(如xorp())与让helper函数成为派生类不重写的虚拟函数(bar())之间有区别吗

通过阅读关于类对象布局和VTABLEs(第28-35张幻灯片)的演示文稿,我没有发现任何区别,因为非虚拟函数和非重写虚拟函数都指向同一个位置(即基类中的函数)


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

您的示例中的缺陷是您没有使用多态性。您可以直接对所有对象进行操作。您不会注意到任何与重写相关的内容,因为这些调用都不需要动态解析。如果调用不是动态解析的,那么虚拟函数和非虚拟函数之间绝对没有区别。要查看差异,请使用无助手函数:

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

int main() {
  // As before
  baz(a);
  baz(b);
  baz(c);
}
现在,您应该能够看到对
foo
bar
baz
的调用的解析方式有明显的不同。特别是

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

。。。在
baz
中不再为真

助手函数是非虚拟成员函数(如xorp())与助手函数是派生类不重写的虚拟函数(bar())之间是否有区别

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

这并不是说现在还没有“差异”

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

另外,编译器不能总是知道是否要使用虚拟重写。因此,它无法优化虚拟调度的额外代码——即使您没有“利用”它。(有时,它可能会,比如如果您有一个从未派生的类,并且没有将其导出……虚拟机可能会被删除。)


对于导出的类,您希望其他人使用:仅仅因为您从未重写过一个方法,并不意味着其他人不会。除非您使用
final
并阻止对象的派生(除非您有充分的理由,否则这样做并不十分友好)。因此,如果你把某个东西变成虚拟的,那么这个功能就存在了,一旦其他人添加了覆盖,那么是的——在这一点上会有所不同。

“你的例子中的缺陷”——我认为这个例子是专门用来问当不使用多态性时编译器是否会做任何不同的事情。@HostileFork——我不知道,OP问“有人能给我举个例子,说明这两种方法会产生不同的结果,或者我没有发现一个警告吗?”,这对我来说意味着他们也不确定他们的例子。但是如果你认为另一个答案适用,请务必写出来。答案多样性是SO的优势。谢谢,baz()的例子确实帮助我更好地理解了我想问的问题。没有区别。但是派生类可能会覆盖函数。通常的警告是,你不知道什么时候发生的,谁做的,他为什么选择这样做。这样做可能会破坏你的类。这些是你在设计时必须考虑的高风险因素。很难正确执行。幸运的是,有一个非常简单的解决方案,声明类
final
。这可能会在将来某个时候产生一个电话,但它可以消除所有这些风险。这里不使用虚拟继承。
void baz(A& a) {
  a.foo();
  a.bar();
  a.xorp();
}

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