现代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/
