这个 final
说明符 在C++中 标记a 班 或虚拟成员函数 作为一个不能从 或 被覆写 . 例如,考虑以下代码:
struct base { virtual void f() const = 0; }; struct derived final : base { void f() const override {} };
如果我们试图编写一个从“derived”派生的新类,则会出现编译器错误:
struct oh_no : derived { };
<source>(9): error C3246: 'oh_no': cannot inherit from 'derived' as it has been declared as 'final' <source>(5): note: see declaration of 'derived'
这个 final
说明符 对于向代码的读者表示类不从中派生并让编译器强制执行这一点很有用,但它也可以通过帮助提高性能 非虚拟化 .
虚拟函数需要通过 函数表 ,比直拨电话贵 由于 与…的互动 分支预测和 指令缓存, 还有 预防 进一步的优化 之后执行 内联 这个 呼叫 .
非虚拟化 是一种编译器优化,它试图在编译时而不是运行时解析虚拟函数调用。 这个 消除 全部的 这个 问题 如上所述, 所以 它 可以 大大提高了使用大量虚拟调用的代码的性能 1 .
下面是一个简单的例子 非虚拟化 :
struct dog { virtual void speak() { std::cout << "woof"; } }; int main() { dog fido; fido.speak(); }
在这段代码中,即使 dog::
speak
是一个虚函数,唯一可能的结果是 main
是否输出 ”woof”
. 如果你看看 编译器输出 您将看到MSVC、GCC和Clang都认识到这一点,并将 dog::speak
进入之内 main
,避免了 n间接 打电话。
这个 final
说明符 可以为编译器提供更多的 非虚拟化 通过帮助它识别更多可以在编译时解决虚拟调用的情况。回到我们最初的例子:
struct base { virtual void f() const = 0; }; struct derived final : base { void f() const override {} };
考虑这个函数:
void call_f(derived const& d) { d.f(); }
自 derived
已标记 final
编译器知道它不能从进一步的 . 这意味着 f
只会打电话来 derived::
f
,因此可以在编译时解析调用。作为证明,这里是 call_f
在MSVC上什么时候 derived
或 derived::
f
标记为 final
:
ret 0
你可以看到 derived::
f
已经 内联 对…的定义 call_f
. 如果我们采取 final
说明符离开了定义 装配看起来 这样地:
mov rax, QWORD PTR [rcx] rex_jmp QWORD PTR [rax]
此代码加载 函数表 从 d
,然后间接调用 derived::
f
通过存储在相关位置的函数指针。
指针加载和跳转的成本可能不会 看 喜欢 许多的 因为只有两条指令,但请记住,这可能涉及分支预测失误和/或指令缓存未命中,这将导致管道暂停。此外,如果有更多的代码 call_f
或者调用它的函数,考虑到将要执行的代码的完整可见性以及这将启用的额外分析,编译器可能能够更积极地优化它。
将类或成员函数标记为 final
可以改进 通过给编译器更多的机会在编译时解析虚拟调用来提高代码的性能。
考虑一下您的代码库中是否有任何地方可以从中受益,并测量其影响!
1 http://assemblyrequired.crashworks.org/how-slow-are-virtual-functions-really/
https://sites.cs.ucsb.edu/~urs/oocsb/papers/oopsla96.pdf
https://stackoverflow.com/questions/449827/virtual-functions-and-performance-c