现代C++学习—— final 优化虚函数调用
我们都知道,在C++中,虚函数的调用是比较耗时的一种操作。
首先需要查询虚表,然后跳转,调用。
实际上,影响虚函数速度的关键是不能进行内联优化
这里我们会写一些UB的代码,来查看,是否调用了虚函数。
这里你可能需要严格鸽:C++虚函数表的位置——从内存的角度
我们看一下以下代码输出什么
#include <iostream> struct A { public: virtual void foo() { std::cout << "A \n"; } int x, y; }; struct B : A{ void foo() override {std::cout<<"B \n";}; }; struct C : A{ public: virtual void foo() { std::cout << "C \n"; } }; int main() { using u64 = unsigned long long; A *a = new A{}; { a->foo(); } C * c = new C{}; { *(u64 *)a = *(u64 *)c; a->foo(); } { A aa = * a; aa.foo(); } { A &aa = *a; aa.foo(); } return 0; }
可以思考一下
实际上,输出结果为
A
C
A
C
我们来解释一下
第一次输出A,这个很简单,就是函数调用。
接下来这个句话
*(u64 *)a =*(u64 *)c;
他是把,a的虚表,换成了c的。
接下来再调用foo,就是c的foo了。
所以第二次输出是C
第三次,第四次输出,需要知道,只有指针和引用才有多态,才会去查虚表。
也就是
A aa =* a; aa.foo();
是不需要查虚表,所以输出A,而引用要,但我们改了虚表,所以是C
再看另外一份代码
#include <iostream> struct A { public: virtual void foo() { std::cout << "A \n"; } int x, y; }; struct B : A{ void foo() override {std::cout<<"B \n";}; }; struct C : A{ public: virtual void foo() { std::cout << "C \n"; } }; int main() { using u64 = unsigned long long; A *a = new B{}; C * c = new C{}; *(u64 *)a = *(u64 *)c; a->foo(); static_cast<B*>(a)->foo(); B * b = static_cast<B*>(a); b->foo(); return 0; }
输出
C
C
C
感觉和上面很像?
但是如果我们稍微改一下,给B加个final
#include <iostream> struct A { public: virtual void foo() { std::cout << "A \n"; } int x, y; }; struct B final : A{ void foo() override {std::cout<<"B \n";}; }; struct C : A{ public: virtual void foo() { std::cout << "C \n"; } }; int main() { using u64 = unsigned long long; A *a = new B{}; C * c = new C{}; *(u64 *)a = *(u64 *)c; a->foo(); static_cast<B*>(a)->foo(); B * b = static_cast<B*>(a); b->foo(); return 0; }
输出就变成了
C
B
B
为什么呢?
这实际上是一个优化,考虑到虚函数的使用场景
一个类,有子类了,我用父类的指针指向了子类,在编译期不知道调用的是哪个函数。
但是如果用final修饰了呢?
我都没有子类了,所以B * 调用的函数是可以在编译期确定的,也就不需要查虚函数表了。
这种优化,叫做去虚拟化
https://quuxplusone.github.io/blog/2021/02/15/devirtualization/