【问题标题】:Difference between using non-virtual base class functions versus derived class non-implemented virtual functions使用非虚基类函数与派生类非实现虚函数之间的区别
【发布时间】:2019-04-14 09:01:22
【问题描述】:

这个问题和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())之间有区别吗? /强>?

通读这个关于类对象布局和 VTABLE 的演示文稿(https://www.cs.bgu.ac.il/~asharf/SPL/Inheritance.pptx,幻灯片 28-35)我无法真正发现区别,因为非虚拟和非覆盖的虚拟函数都指向同一个地方(即基类中的函数)

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

【问题讨论】:

  • 没有区别。但是派生类可能会覆盖该函数。通常的警告是,您不知道它何时发生,是谁做的以及他为什么选择这样做。并且可以打破你的班级这样做。这些是您在设计时必须考虑的高风险因素。很难正确地做。幸运的是,有一个非常简单的解决方案,声明类final。这可能会在未来某个时间产生一个电话,但它可以消除所有这些风险。
  • 这里没有使用虚拟继承

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


【解决方案1】:

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

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

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

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

另外,编译器并不总是知道您是否要使用虚拟覆盖。所以它不能优化虚拟调度的额外代码——即使你没有“利用”它。 (有时它可以,比如如果你有一个你从不派生的类并且不导出它......虚拟可能会被丢弃。)

对于导出的类,您希望其他人使用:仅仅因为 从不覆盖方法并不意味着其他人不会。除非你使用final 并防止派生你的对象(这被认为不是非常友好,除非你有很好的理由)。因此,如果您将某些东西虚拟化,则该功能就在那里,并且一旦其他人添加了覆盖,那就可以了--那时就会有所不同。

【讨论】:

    【解决方案2】:

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

    void baz(A& a) {
      a.foo();
      a.bar();
      a.xorp();
    }
    
    int main() {
      // As before
      baz(a);
      baz(b);
      baz(c);
    }
    

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

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

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

    【讨论】:

    • “你的例子中的缺陷” - 我认为这个例子是专门用来询问编译器在不使用多态性时是否会做任何不同的事情。
    • @HostileFork - 我不知道,OP 问 “谁能给我一个例子,说明这两种方法会产生不同的结果,或者是否有我没有发现的警告?”,这对我来说意味着他们也不确定他们的例子。但是,如果您认为另一个答案适用,请务必写下来。答案的多样性是 SO 的强项。
    • 谢谢,用 baz() 的例子确实帮助我更好地理解了我想要问的问题。
    猜你喜欢
    • 2015-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-18
    相关资源
    最近更新 更多