不幸的是,Luchian Grigore的答案是不正确的。
在Diamond 继承的情况下是正确的。如果你有一个继承自 B 和 C 的类 D,它本身继承自 A。如果你调用 D 的函数,将对 vtable 进行添加操作以查找 D 引用。
(e.g) in Memory
------- Classes hierarchy
---> | A | A
the offset here | ------- / \
is the pointer -> | | ... | B C <- Diamond Inheritance
addition he is | ------- \ / (usually very bad)
talking about ---> | D | D
-------
在您的情况下,对于多态调用,编译器必须对 vtable 进行查找(读取)才能获得您正在调用的正确函数。当 vtable 指向的数据没有被缓存时,情况会变得更糟。在这种情况下,您会遇到缓存未命中,这是非常昂贵的。
此外,每个类有一个 vtable,并且该类的每个对象共享同一个 vtable。请参阅在 C++ 中,什么是 vtable,它是如何工作的?
您问题的真正答案取决于您将在这 30 个类中的每一个之间交换多少次,以及您使用它们的频率。会循环使用吗?
您描述的解决方案通常用于解决此潜在问题。但实际上,它可能与多态调用一样快,具体取决于它的使用方式。
所以一般来说你可以选择多态方法而不用担心成本,因为它可以忽略不计。首先编写干净且可维护的代码,然后再进行优化。:)
编辑 :
由于在这个线程上讨论了实际的编译器代码,我决定运行一个示例来找出真正发生的事情。
下面你可以看到我使用的代码示例。
#include <stdlib.h>
#include <stdio.h>
class I
{
public:
virtual void f(void) = 0;
};
class A : public I
{
public:
void f(void)
{
printf("A\n");
}
};
int main(int argc, char* argv[])
{
__asm
{
int 3
}
A* pA = new A();
__asm
{
nop
nop
}
pA->f();
__asm
{
nop
nop
}
A a;
a.f();
__asm
{
nop
nop
}
return 0;
}
然后,您可以看到示例的实际汇编代码(编译器如何解释它)。
int main(int argc, char* argv[])
{
__asm
{
int 3
010E1010 int 3
}
A* pA = new A();
010E1011 push 4
010E1013 call operator new (10E10A4h)
010E1018 add esp,4
010E101B test eax,eax
010E101D je main+17h (10E1027h)
010E101F mov dword ptr [eax],offset A::`vftable' (10E2104h)
010E1025 jmp main+19h (10E1029h)
010E1027 xor eax,eax
__asm
{
nop
010E1029 nop
nop
010E102A nop
}
pA->f();
010E102B mov edx,dword ptr [eax]
010E102D mov ecx,eax
010E102F mov eax,dword ptr [edx]
010E1031 call eax
__asm
{
nop
010E1033 nop
nop
010E1034 nop
}
A a;
a.f(); //Polymorphic call
010E1035 push offset string "A\n" (10E20FCh)
010E103A call dword ptr [__imp__printf (10E20ACh)]
010E1040 add esp,4
__asm
{
nop
010E1043 nop
nop
010E1044 nop
}
return 0;
010E1045 xor eax,eax
}
class A : public I
{
public:
void f(void)
{
printf("A\n");
010E1000 push offset string "A\n" (10E20FCh)
010E1005 call dword ptr [__imp__printf (10E20ACh)]
010E100B pop ecx
}