将内联函数作为参数传递

Passing inline functions as arguments

本文关键字:参数传递 函数      更新时间:2023-10-16

我想知道当一个函数作为agument传递时,C++是否仍会遵守inline关键字。在以下示例中,每次在while循环中调用frame()时,是否都会将onFrame的新帧推送到堆栈上?

bool interrupt = false;
void run(std::function<void()> frame) {
    while(!interrupt) frame();
}
inline void onFrame() {
    // do something each frame
}
int main() {
    run(onFrame);
}

或者改成这样会有什么影响吗?

void run(std::function<inline void()> frame) {
    while(!interrupt) frame();
}

如果你没有明确的答案,你能帮我找到一种方法来测试吗?可能使用内存地址或某种调试器?

如果编译器必须通过std::function的类型擦除调度才能实现,那么它将很难内联您的函数。无论如何,这都有可能发生,但你正在尽可能地让它变得艰难。你提出的备选方案(采用std::function<inline void()>论点)形式不正确。

如果您不需要类型擦除,请不要使用类型擦除。run()可以简单地采用任意可调用的:

template <class F>
void run(F frame) {
    while(!interrupt) frame();
}

这使得编译器更容易内联muuch。尽管如此,仅仅拥有一个inline函数本身并不能保证该函数内联。看看这个答案。

还要注意,当您传递函数指针时,这也会降低内联的可能性,这很尴尬。我试图在这里找到一个有一个很好例子的答案,但在那之前,如果内联非常重要,那么将其封装在lambda中可能是最好的方法:

run([]{ onFrame(); });

仍然遵守inline关键字。。。一个新的框架。。。被推到堆栈上

inline关键字一开始并不是这样做的(请参阅此问题以获得广泛的参考)。


正如Barry所做的那样,假设您希望说服优化器内联您的函数调用(再次幸运地说:这与inline关键字无关),那么函数模板+lambda可能是最佳选择。

要了解原因,请考虑优化器在以下每种情况下都必须使用什么:

  1. 函数模板+lambda

    template <typename F>
    void run(F frame) { while(!interrupt) frame(); }
    // ... call site ...
    run([]{ onFrame(); });
    

    在这里,函数只存在于调用站点(从模板中实例化),优化器需要在范围内工作并定义良好。

    请注意,如果优化器认为额外的指令缓存压力将超过堆栈帧的节省,那么它仍然可以合理地选择不内联调用

  2. 功能指针

    void run(void (*frame)()) { while(!interrupt) frame(); }
    // ... call site ...
    run(onFrame);
    

    在这里,run可能必须作为一个独立的函数进行编译(尽管如果链接器能够证明没有人使用它,那么该副本可能会被丢弃),onFrame也是如此,尤其是因为它的地址被占用了。最后,优化器在决定是否内联这些调用时,可能需要考虑是使用许多不同的函数指针调用run,还是仅使用一个。总的来说,这似乎是更多的工作,并可能最终成为一个链接时间优化。

    注:。我用"独立函数"来表示编译器可能会发出代码&在这两种情况下,正常自由函数的符号表条目。

  3. std::function

    这已经越来越长了。让我们注意到,这个类花了很大的篇幅(Barry提到的类型的擦除)来使函数

    void run(std::function<void()> frame);
    

    不是取决于函数的确切类型,这意味着在编译器为run生成代码时向编译器隐藏信息,这意味着您可以使用更少的信息(或者相反,需要更多的工作来撤消所有谨慎的信息隐藏)。


至于测试您的优化器所做的工作,您需要在整个程序的上下文中对此进行检查:根据代码大小和复杂性,可以自由选择不同的启发式方法。

为了完全确定实际做了什么,只需使用源代码进行反汇编或编译到汇编程序即可。(是的,这可能是一个很大的"公正",但它是特定于平台的,不是真正针对问题的,而且无论如何都是一项值得学习的技能)。

编译以进行发布并检查列表文件,或者在调试器中打开反汇编。最好的方法是检查生成的代码。