在重型循环中,例如在游戏应用程序中发现的循环中,可能有许多因素决定循环体的哪个部分被执行(例如,角色对象将根据其当前状态进行不同的更新),因此做:
void my_loop_function(int dt) {
if (conditionX && conditionY)
doFoo();
else
doBar();
...
}
我习惯使用指向对应于角色当前状态的特定逻辑功能的函数指针,如:
void (*updater)(int);
void something_happens() {
updater = &doFoo;
}
void something_else_happens() {
updater = &doBar;
}
void my_loop_function(int dt) {
(*updater)(dt);
...
}
在我不想做任何事情的情况下,我定义了一个虚拟函数,并在需要时指向它:
void do_nothing(int dt) { }
现在我真正想知道的是:我是否在不必要地迷恋这个?上面给出的例子当然很简单;有时候我需要检查很多变量来弄清楚我需要执行哪些代码,所以我想出使用这些“状态”函数指针确实会更优,对我来说,自然,但有些人我处理时非常不同意。
那么,使用(虚拟)函数指针值得它的收益,而不是用条件语句填充我的循环来流动逻辑吗?
编辑:为了阐明如何设置指针,它是通过基于每个对象的事件处理来完成的。当一个事件发生时,比如说该字符附加了自定义逻辑时,它会在该事件处理程序中设置updater指针,直到另一个事件发生,这将再次改变流程。
谢谢
答案 0 :(得分:6)
函数指针方法让你使转换异步。而不是仅仅将dt
传递给updater
,也可以传递对象。现在更新程序本身可以负责状态转换。这将本地化状态转换逻辑,而不是将其全局化为一个大的丑陋if ... else if ... else if ...
函数。
就这种间接成本而言,你关心吗?您可能会关心更新程序是否非常小,以至于取消引用和函数调用的成本超过了执行更新程序代码的成本。如果更新程序具有任何复杂性,那么这种复杂性将超过这种额外灵活性的成本。
答案 1 :(得分:4)
我想我会同意这里的非信徒。在这种情况下,金钱问题是如何设置指针值?
如果你能以某种方式索引到map
并生成一个指针,那么这种方法可能会通过降低代码复杂性来证明自己的合理性。但是,你在这里所拥有的更像是分布在多个函数中的状态机。
考虑到something_else_happens
实际上必须先检查指针的前一个值,然后再将其设置为另一个值。同样适用于something_different_happens
等。实际上,你已经将状态机的逻辑分散到各处并使其难以理解。
答案 2 :(得分:2)
现在我真正想知道的是:我是不是在不必要地迷恋这个?
如果您实际上没有运行代码,并且发现它实际上运行得太慢,那么是的,我认为您可能过早担心性能。
<{3}}中的Herb Sutter和Andrei Alexandrescu将第8章专门用于此,称为“不要过早优化”,他们总结得很好:刺激不是一个愿意的马(拉丁语谚语):过早优化会让人上瘾,因为它没有生产力。优化的第一条规则是:不要这样做。优化的第二条规则(仅限专家)是:不要这样做。测量两次,优化一次。
还值得一读第9章:“不要过早地悲观”
答案 3 :(得分:0)
测试条件是:
执行间接是:
它可能更高效!
事实上,您之前在另一个地方进行“比较”,以决定要拨打什么电话。结果将是相同的。 您没有注意到调度系统与调用虚拟函数时编译器所执行的调度系统相同。 事实证明,避免通过交换机实现调度的虚函数并不能提高现代编译器的性能。
“不使用间接/不使用虚拟/不使用函数指针/不动态转换等”在大多数情况下,只是基于早期编译器和硬件架构的历史限制的神话。
答案 4 :(得分:0)
性能差异取决于硬件和编译器 优化。在某些机器上,间接调用可能非常昂贵 别人很便宜。真正优秀的编译器可能能够优化 甚至间接调用,基于探查器输出。直到你真的 对您的实际目标硬件和使用的两种变体进行基准测试 您在最终版本代码中使用的编译器和编译器选项 不可能说。
如果间接通话最终过于昂贵,您仍然可以提升
通过设置enum
并使用a来完成循环测试
循环中switch
,或者通过为每个组合实现循环
设置,并在开头选择一次。 (如果你的功能
指向实现完整的循环,这几乎肯定会
比每次通过循环测试条件更快,即使
间接是昂贵的。)