这种手动去虚拟化是否合法/可行?
Is this manual devirtualization legal / viable?
对于我的一个项目,我终于需要使用我的第一个多态 class(std::cout 除外)。
我正在研究如何确保至少在某些情况下我有 100% 的去虚拟化调用。
此代码是否合法可行?
dynamic_cast
有多慢?如果我在构造函数中支付一次,那么我将始终可以直接使用 class B
,所以这听起来是个好习惯?
在那之后 C 风格转换会很快还是我必须存储指向 B
的指针?
struct A{
virtual void x();
};
struct B : A{
void x() override;
};
struct X{
A *a;
bool fB;
X(A &a) : a(&a), fB( dynamic_cast<B *>(&a) ){}
void f(){
if (fB){
((B*)a)->x();
}else{
a->x();
}
}
};
void fn(A &a){
X x(a);
x.f();
}
int main(){
B b;
X x(b);
x.f();
}
您尝试将虚拟调用更改为分支。
我不确定它是否更快,
但更糟糕的是,您不删除虚拟调用,因为 B
可以有子子项,您至少必须创建 void B::x() final
以允许编译器将调用去虚拟化。
如果编译器可以像 main
那样访问具体类型(和代码),它可能能够单独对调用进行去虚拟化。
对于您现有的代码,我实际上希望您的编译器优化掉 if 语句和您的转换,因为这是对代码的简化。
从编译器的角度来看,您的代码库可能如下所示:
struct A{
virtual void x();
};
struct B : A{
void x() override;
};
struct C : B{
void x() override;
};
因此,当它看到您的转换和函数调用时:static_cast<B*>(a)->x()
它仍然必须访问与调用 a->x()
时相同的虚拟 table,因为可能存在潜在的 class C.
(请注意,我使用 static_cast,因为 C 风格的转换是错误的来源)
如果你想直接调用B,你最好使方法或classfinal
。另一个好的方法是使用配置文件引导优化,在这种情况下,他们经常比较 vtable 指针。
回答你的问题?
- 代码合法吗?是的,是的。
- 这可行吗?是的,鉴于上述评论。
- dynamic_cast会有多慢?慢,我会争辩为此写一个好的基准,但是,我不知道如何这样做并使它现实。
- 这是好的做法吗?不,它使多态性变得不太可用。
- static_cast会快吗?是的,它很快,在这种特定情况下,不需要任何说明。与 bool 相比,存储指向 B 的指针在复杂继承的特定情况下都可以得到改进。如果它需要额外的内存,它也可能导致性能下降。只需在您的 executable.
中添加额外的程序集,另一个减少可能会很好
我的建议,特别是因为您是 C++ 的新手:不要执行此操作或任何其他手动优化。编译器可以为你做很多事情。每次手动优化都会导致额外的维护,只有在您确实遇到性能问题时才使用这些技巧。
哦,如果你想知道实际的汇编代码是什么,你可以在compiler explorer
进行实验
对于我的一个项目,我终于需要使用我的第一个多态 class(std::cout 除外)。
我正在研究如何确保至少在某些情况下我有 100% 的去虚拟化调用。
此代码是否合法可行?
dynamic_cast
有多慢?如果我在构造函数中支付一次,那么我将始终可以直接使用 class B
,所以这听起来是个好习惯?
在那之后 C 风格转换会很快还是我必须存储指向 B
的指针?
struct A{
virtual void x();
};
struct B : A{
void x() override;
};
struct X{
A *a;
bool fB;
X(A &a) : a(&a), fB( dynamic_cast<B *>(&a) ){}
void f(){
if (fB){
((B*)a)->x();
}else{
a->x();
}
}
};
void fn(A &a){
X x(a);
x.f();
}
int main(){
B b;
X x(b);
x.f();
}
您尝试将虚拟调用更改为分支。
我不确定它是否更快,
但更糟糕的是,您不删除虚拟调用,因为 B
可以有子子项,您至少必须创建 void B::x() final
以允许编译器将调用去虚拟化。
如果编译器可以像 main
那样访问具体类型(和代码),它可能能够单独对调用进行去虚拟化。
对于您现有的代码,我实际上希望您的编译器优化掉 if 语句和您的转换,因为这是对代码的简化。
从编译器的角度来看,您的代码库可能如下所示:
struct A{
virtual void x();
};
struct B : A{
void x() override;
};
struct C : B{
void x() override;
};
因此,当它看到您的转换和函数调用时:static_cast<B*>(a)->x()
它仍然必须访问与调用 a->x()
时相同的虚拟 table,因为可能存在潜在的 class C.
(请注意,我使用 static_cast,因为 C 风格的转换是错误的来源)
如果你想直接调用B,你最好使方法或classfinal
。另一个好的方法是使用配置文件引导优化,在这种情况下,他们经常比较 vtable 指针。
回答你的问题?
- 代码合法吗?是的,是的。
- 这可行吗?是的,鉴于上述评论。
- dynamic_cast会有多慢?慢,我会争辩为此写一个好的基准,但是,我不知道如何这样做并使它现实。
- 这是好的做法吗?不,它使多态性变得不太可用。
- static_cast会快吗?是的,它很快,在这种特定情况下,不需要任何说明。与 bool 相比,存储指向 B 的指针在复杂继承的特定情况下都可以得到改进。如果它需要额外的内存,它也可能导致性能下降。只需在您的 executable. 中添加额外的程序集,另一个减少可能会很好
我的建议,特别是因为您是 C++ 的新手:不要执行此操作或任何其他手动优化。编译器可以为你做很多事情。每次手动优化都会导致额外的维护,只有在您确实遇到性能问题时才使用这些技巧。
哦,如果你想知道实际的汇编代码是什么,你可以在compiler explorer
进行实验