我正在用C ++编写一个游戏,它有大约30个不同的角色,每个角色都略有不同。我有一个主类User,其中包含所有角色所需的所有数据。我的第一个实现只涉及30个角色的枚举和适当的处理,但现在我想知道将User作为基类并且每个角色都是自己继承自User的类是否更好。
我主要担心的是,当有30多个类继承自单个基类时,多态方法调用的效率如何?我知道多态调用涉及虚拟表中的指针,但我不确定这是否意味着对整个表进行线性搜索以获得正确的方法,或者是否可以在恒定时间内完成。
如果有人可以用多个继承的类来评论多态方法调用的效率,我会很感激启发。
提前致谢!
答案 0 :(得分:7)
使用合成,而不是使用继承。只需为“用户”类提供一个扮演角色的对象即可。所以你最终可能会有30个“角色”课程。但是它们并没有继承“用户”,而是被赋予“用户”使用(他们可以继承自己的基础抽象类来定义“角色”的界面)
或者它只是一个函数....你可能只是将它建模为一堆函数对象,然后将它们传递给User。
答案 1 :(得分:5)
成本是可以忽略的。无论你有多少个类,或者有多少级别的继承,多态调用的成本都是一个简单的加法 - 指向vftable
的指针加上特定函数的偏移量(非标准强制,但在大多数情况下,如果不是全部,那么这是正确的。)
答案 2 :(得分:0)
不幸的是,Luchian Grigore的答案不正确。
Diamond inheritance的情况是正确的。你有一个继承自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所指向的数据不会被缓存。在这种情况下,你有一个cache miss,这是非常昂贵的。
此外,每个类有一个vtable,该类的每个对象共享相同的vtable。见In C++, what’s a vtable and how does it work?
您问题的真正答案取决于您在这30个班级中的每个班级之间进行多少次交换以及您使用它们的频率。它会在循环中使用吗?
您所描述的解决方案通常用于解决此潜在问题。但实际上,它可能与多态调用一样快,具体取决于它的使用方式。
所以一般来说,你可以选择Polymorphic方法而不用担心成本,因为它是可以忽略不计的。首先编写干净且可维护的代码,稍后进行优化。 :)
由于在这个帖子上有关于实际编译器代码的讨论,我决定运行一个样本来找出真实情况。
下面你可以看到我使用的代码示例。
#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
}