即使没有 sub-class 重写方法,它也会被虚拟调用吗?

Will a method be called virtually even if no sub-class overrides it?

假设您有以下 类:

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 的 sub-类 的 none 会覆盖该方法,所以无论我将什么分配给指向 B 的指针 B * b = new C; b->print();,它总是会调用 B::print().

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

Theoretically it can be determined at compile-time, because we know, that none of sub-classes of B overrides that method

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

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

关于

Theoretically it can be determined at compile-time, because we know, that none of sub-classes of B overrides that method, so no matter what i assign into pointer to B B * b = new C; b->print();, it will always call B::print().

是。

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

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

  • 是否在多个编译单元中分别定义了类?

  • 你在使用全局优化吗?

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

以你的具体例子,

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

其中 print 是虚拟的,我非常有信心无论编译器如何,它都会被非虚拟地调用,因为在这里编译器 知道 什么 b指的是。让我们检查一下。

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

除了标准之外,现代编译器还可以编译诸如 DLL 和共享库之类的东西,并处理其他进程 new 将一些 object 实例放入其中的原始共享内存的情况。

所以经常会看到 "Common" 或 "Shared" 文件夹,其中包含一些 class 的接口,并且有两个项目包括 header 和派生自那个界面。

所以在你的例子中,假设整个声明都在一个 header 文件中,其他一些项目可能包含 header 并派生自 B

这是从 DLL 导出 class 指针并从共享库调用正确函数的方法。

正如我在评论中所写:
虚函数有开销,但无论如何要多 2~3 条流水线。您将 v-table 加载到某个寄存器中,将该寄存器增加一些偏移量,您就完成了。您将不得不调用虚拟函数数百万次才能看到一秒钟的差异。通常的瓶颈是 non-friendly 缓存变量、内存分配、繁重的 CPU 和 IO,而不是虚拟函数