即使没有 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,而不是虚拟函数
假设您有以下 类:
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,而不是虚拟函数