我可以坚持GCC通过存储的函数指针内联延迟调用吗?

时间:2015-03-02 22:54:51

标签: c++ c++03

当然,当在该范围内直接知道内部函数调用时,C ++编译器可以内联函数模板中的函数调用(ref)。

#include <iostream>

void holyheck()
{
   std::cout << "!\n";
}

template <typename F>
void bar(F foo)
{
   foo();
}

int main()
{
   bar(holyheck);
}

现在如果我将holyheck传递给一个类,该类存储函数指针(或等效函数)并稍后调用它,该怎么办?我有希望得到内联吗?怎么样?

template <typename F>
struct Foo
{
   Foo(F f) : f(f) {};
   void calledLater() { f(); }

private:
   F f;
};

void sendMonkeys();
void sendTissues();

int main()
{
   Foo<void(*)()> f(sendMonkeys);
   Foo<void(*)()> g(sendTissues);
   // lots of interaction with f and g, not shown here
   f.calledLater();
   g.calledLater();
}

我的类型Foo旨在隔离大量逻辑;它将被实例化几次。从calledLater调用的特定函数是实例化之间唯一不同的东西(虽然它在Foo的生命周期内永远不会改变),因此Foo的一半目的是遵守干。 (其余的目的是保持这个机制与其他代码隔离。)

但我不想在这样做时引入实际附加函数调用的开销,因为这都是在程序瓶颈中发生的。

我不会说ASM所以分​​析编译后的代码对我来说并不是很有用。
我的直觉是我没有机会在这里内嵌。

3 个答案:

答案 0 :(得分:5)

如果你真的不需要使用函数指针,那么仿函数应该使优化变得微不足道:

struct CallSendMonkeys {
  void operator()() {
    sendMonkeys();
  }
};
struct CallSendTissues {
  void operator()() {
    sendTissues();
  }
};

(当然,C ++ 11有lambdas,但你标记了你的问题C ++ 03.)

通过对这些类进行Foo的不同实例化,并且这些类中没有内部状态,f()不依赖于f的构造方式,因此它是{s}}如果编译器无法证明它仍未被修改,那就没问题了。

答案 1 :(得分:1)

通过你的例子,在进行编译后进行编译看起来像这样:

template <typename F>
struct Foo
{
   Foo(F f) : f(f) {};
   void calledLater() { f(); }

private:
   F f;
};

void sendMonkeys();
void sendTissues();

int main()
{
    Foo<__typeof__(&sendMonkeys)> f(sendMonkeys);
    Foo<__typeof__(&sendTissues)> g(sendTissues);
   // lots of interaction with f and g, not shown here
   f.calledLater();
   g.calledLater();
}

clang ++(几周之前的3.7,这意味着我希望clang ++ 3.6能够做到这一点,因为它在源代码中只有几个星期了)会产生这样的代码:

    .text
    .file   "calls.cpp"
    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %entry
    pushq   %rax
.Ltmp0:
    .cfi_def_cfa_offset 16
    callq   _Z11sendMonkeysv
    callq   _Z11sendTissuesv
    xorl    %eax, %eax
    popq    %rdx
    retq
.Ltmp1:
    .size   main, .Ltmp1-main
    .cfi_endproc

当然,如果没有sendMonkeys和sendTissues的定义,我们就无法进一步内联。

如果我们像这样实施它们:

void request(const char *);
void sendMonkeys() { request("monkeys"); }
void sendTissues() { request("tissues"); }

汇编程序代码变为:

main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %entry
    pushq   %rax
.Ltmp2:
    .cfi_def_cfa_offset 16
    movl    $.L.str, %edi
    callq   _Z7requestPKc
    movl    $.L.str1, %edi
    callq   _Z7requestPKc
    xorl    %eax, %eax
    popq    %rdx
    retq

.L.str:
    .asciz  "monkeys"
    .size   .L.str, 8

    .type   .L.str1,@object         # @.str1
.L.str1:
    .asciz  "tissues"
    .size   .L.str1, 8

如果您无法读取汇编程序代码,则request("tissues")request("monkeys")按预期内联。

我只是惊讶于g ++ 4.9.2。并没有做同样的事情(我做到了这一点,并期望继续&#34;而g ++也是这样做的,我不会为它发布代码&#34;)。 [它内联sendTissuessendMonkeys,但不会进行下一步内联request

当然,完全可以对此进行微小的更改而不是内联代码 - 例如添加一些依赖于编译器无法在编译时确定的变量的条件。 / p>

修改 我确实在Foo中添加了一个字符串和一个整数,并使用外部函数更新了这些内容,此时内联对于clang和gcc都消失了。使用JUST一个整数并调用外部函数,它会内联代码。

换句话说,它实际上取决于该部分中的代码 // lots of interaction with f and g, not shown here。而且我认为你(Lightness)已经在这里呆了很长时间才能知道,对于80%以上的问题,它的代码并没有发布在问题中,这是实际问题中最重要的部分。回答;)

答案 2 :(得分:1)

要使原始方法有效,请使用

template< void(&Func)() >
struct Foo
{
    void calledLater() { Func(); }
};

一般来说,通过使用函数引用而不是函数指针,我可以更好地获得gcc内联内容。