即使没有子类覆盖它,也会虚拟调用一个方法吗?

时间:2016-04-11 18:12:50

标签: c++ call virtual

想象一下,您有以下课程:

class A {
  public:
    virtual void print()           { printf("A\n"); }
};
class B : public A {
  public:
    virtual void print() override  { printf("B\n"); }
};
class C : public B {
    // no override of print
};

现在,如果您创建B的实例并调用print:

B * b = new B;
b->print();

这种方法会被虚拟调用吗?换句话说,在编译时或运行时确定要调用的确切方法吗?

理论上它可以在编译时确定,因为我们知道,B的子类都不会覆盖该方法,因此无论我分配到指向B B * b = new C; b->print();的指针,它总是会调用{ {1}}。

编译器是否也知道它并使我免于虚拟调用的不必要开销?

3 个答案:

答案 0 :(得分:3)

  

从理论上讲,它可以在编译时确定,因为我们知道,B的子类都不会覆盖该方法

在一般情况下,您无法在编译时确定这一点,因为C ++编译器一次只处理一个转换单元。来自不同翻译单元的类,例如class D : public B可以覆盖该方法。但是,在转换class D的调用时,编译器可能无法查看b->print()的转换单元,因此编译器必须采用虚拟调用。

为了解决这个缺点,C ++ 11引入了final keyword,它允许程序员告诉编译器,从这个级别的继承层次结构中不会有进一步的覆盖。现在,编译器可以优化虚拟调用,并强制执行不再进行覆盖的要求。

答案 1 :(得分:2)

关于

  

理论上它可以在编译时确定,因为我们知道,B的子类都没有覆盖那个方法,所以无论我指定什么指向BB * b =新C; b-> print();,它将始终调用B :: print()。

然而,编译器是否会进行此优化完全取决于编译器,它知道什么,以及你告诉它做什么。

编译器知道什么取决于许多因素,例如

  • 是否在多个翻译单元中定义了单独编译的类?

  • 您使用的是全局优化吗?

  • 您是否可以使用关键字final来通知编译器?

根据您的具体示例,

B * b = new B;
b->print();

其中print是虚拟的,无论编译器如何,我都会非常自信地称它为非虚拟的,因为编译器知道 b所指的是什么。我们来看看。

好的,使用MinGW g ++ 5.1和选项-O2(我没有尝试其他任何操作),呼叫被编译为直接调用puts,甚至绕过printf

答案 2 :(得分:1)

标准除外,现代编译器还具有DLL和共享库等编译功能,并处理原始共享内存的情况,其他进程new编写了一些对象实例。

所以看到" Common"并不常见。或"共享"保存某个类的接口的文件夹,两个项目包括该标头并从该接口派生。

所以在你的例子中,让我们说整个声明都在一个头文件中,其他一些项目可能包含那个头,并派生自B

这是如何从DLL导出类指针并从共享库调用正确的函数。

正如我在评论中所写: 虚函数有一个开销,但无论如何还要2~3个装配线。你将v表加载到某个寄存器中,将该寄存器递增一些偏移量就完成了。为了看到差异的一秒,你将不得不调用毫秒次的虚拟功能。 usuall瓶颈是非友好的缓存变量,内存分配,无论如何都是CPU和IO,而不是虚函数