多态调用的成本 - C++
Cost of Polymorphic calls - C++
我正在用C++编写一个游戏,它有大约 30 个不同的角色,每个角色都略有不同。我有一个主类 User,其中包含所有角色所需的所有数据。我的第一个实现只涉及枚举 30 个角色并适当地处理,但现在我想知道是否最好将 User 作为基类,并且每个角色都是从 User 继承的自己的类。
我主要关心的是,当有 30+ 个类从单个基类继承时,多态方法调用的效率如何?我知道多态调用涉及虚拟表中的指针,但我不确定这是否意味着在整个表中线性搜索正确的方法,或者是否可以在恒定时间内完成。
如果有人可以评论具有许多继承类的多态方法调用的效率,我将不胜感激。
提前感谢!
与其使用继承,不如使用组合。 只需为您的"User"类提供一个执行该角色的对象即可。 因此,您最终可能会获得30个"角色"类。 但是它们不会继承"User",而是交给"User"使用(它们可以继承自己的基抽象类来定义"Role"的接口)
或者如果它只是一个功能....您可以将其建模为一堆函数对象,然后将其传递给 User。
费用可以忽略不计。不管你有多少类,或者有多少级继承,多态调用的成本是一个简单的加法 - 指向vftable
的指针加上特定函数的偏移量(不是标准授权的,但在大多数(如果不是全部)实现中,这是正确的)。
不幸的是,Luchian Grigore的回答是不正确的。
在钻石继承的情况下是正确的。其中,您有一个从 B 继承的类 D 和 C 本身从 A 继承的 C。如果调用 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("An");
}
};
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 "An" (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("An");
010E1000 push offset string "An" (10E20FCh)
010E1005 call dword ptr [__imp__printf (10E20ACh)]
010E100B pop ecx
}
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- 为什么使用 "this" 指针调用派生成员函数?
- 函数调用中参数的顺序重要吗
- OpenGL - 在抛出"__gnu_cxx::recursive_init_error"实例后终止调用?
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在c++类上调用void函数
- 为什么 std::unique 不调用 std::sort?
- 调用专用模板时出错"no matching function for call to [...]"
- 选择要调用的构造函数
- C++为什么尽管我调用了void函数,它却不起作用
- 构造函数正在调用一个使用当前类类型的函数
- 变量没有改变?通过向量的函数调用
- 没有为自己的结构调用列表推回方法
- 调用'begin(int [n])'没有匹配函数
- 什么时候调用析构函数
- 如何用参数值调用函数(仅在运行时已知)
- std::cout.imbue()多重调用
- 函数何时会在c++中包含stack_Unwind_Resume调用