当然,当在该范围内直接知道内部函数调用时,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所以分析编译后的代码对我来说并不是很有用。
我的直觉是我没有机会在这里内嵌。
答案 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;)。 [它内联sendTissues
和sendMonkeys
,但不会进行下一步内联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
内联内容。