为什么很清楚模板函数实例化不会内联
Why is it clear that a template function instantiation will not be inlined?
关于作为模板参数传递的函数,Ben Supnik提供的社区wiki答案讨论了内联实例化函数模板的问题。
答案是以下代码:
template<typename OP>
int do_op(int a, int b, OP op)
{
return op(a,b,);
}
int add(int a, b) { return a + b; }
int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);
答案是这样的(关于最后一行,它实例化了函数模板do_op
):
很明显,这并没有被内联。
我的问题是:为什么它没有被内联?
他说的(我认为)是add
函数没有内联。换句话说,编译器可以像这样内联do_op
:
int c = func_ptr(4, 5);
但它不会像这样内联add
:
int c = 4 + 5;
然而,在这个简单的例子中,他可能是错的。
通常,当你通过指针调用函数时,编译器(在编译时)不知道你要调用什么函数,所以它不能内联函数。示例:
void f1() { ... }
void f2() { ... }
void callThroughPointer() {
int i = arc4random_uniform(2);
void (*f)() = i ? f2 : f1;
f();
}
这里,编译器无法知道callThroughPointer
将调用f1
还是f2
,因此无法在callThroughPointer
中内联f1
或f2
。
但是,如果编译器能够在编译时证明将调用什么函数,则允许内联该函数。示例:
void f1() { ... }
void f2() { ... }
void callThroughPointer2() {
int i = arc4random_uniform(2);
void (*f)() = i ? f2 : f1;
f = f1;
f();
}
这里,编译器可以证明f
将始终是f1
,因此允许将f1
内联到callThroughPointer2
中。(这并不意味着将内联f1
…)
类似地,在您文章中引用的例子中,编译器可以证明func_ptr
在对do_op
的调用中始终是add
,因此允许内联add
。(这并不意味着将内联add
…)
通过函数指针调用函数时,编译器极不可能避免通过函数指针进行调用。只有当编译器能够证明它知道函数指针是用什么初始化的,并且不能更改它时,它才有可能通过函数指针避免函数调用,从而内联函数。在引用的设置中,即
int (* func_ptr)(int, int) = add;
函数指针func_ptr
是可修改的,因此编译器不能保证它永远不会改变。因此,它不可能内联对add
的调用。
如果代码片段确实是完整的,那么在初始化过程中会发生一些事情,编译器实际上可以知道func_ptr
被初始化为包含add
。
我认为,到目前为止,这里的讨论没有抓住要点。首先,由于语法错误,测试代码甚至无法编译。可能是这样的意思:
template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
int add(int a, int b) { return a + b; }
int (*func_ptr)(int, int) = add;
int c = do_op(4, 5, func_ptr);
// int c = (*func_ptr)(4, 5);
编译时,编译器将发出实际调用add()函数的代码。但是,当在没有int c = (*func_ptr)(4, 5);
模板的情况下编写时,编译器还会发出对add()的调用。这是因为func_ptr
在这个示例代码中是全局定义的,编译器必须注意另一个线程中的某些代码在初始化和后续使用之间修改func_ptr的可能性。但这是全局可见函数指针的属性,与模板无关!除了一些本地标签的名称外,带有优化器的GCC通过func_ptr为add()的模板化和非模板化调用生成完全相同的汇编程序输出。不同的标签名称意味着,由于模板的原因,优化器必须多转一圈,因此编译时间会增加(与所有模板一样),但代码和代码运行时间是相同的。
如果将func_ptr移动到函数内部的局部变量,如以下示例所示,编译器可以确定地跟踪对func_ptr的所有访问,因此可以优化所有内容,甚至不再直接或通过函数指针调用add()函数:
int testf(void) {
int (*func_ptr)(int, int) = add;
return do_op(4, 5, func_ptr);
}
因此,总结一下:通过模板进行的函数调用不会阻止优化器执行其工作。如果在编译时不能安全地确定函数指针的值,则函数指针可能会造成危害,但如果添加了模板,则问题不会恶化。
为什么它没有内联?
不是。编译器没有理由不能内联该代码段中的所有代码。
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 对象实例化调用构造函数的次数太多
- 如何使用非默认构造函数实例化模板化类
- 为什么 gcc 和 clang 为函数模板的实例化生成不同的符号名称?
- 模板化类构造函数的模板实例化
- 何时需要实例化函数模板定义?
- clang:使用 O3 导出隐式实例化函数的符号
- 如何调用模板函数的每个实例化函数
- 使用泛型类型显式实例化函数模板
- 使用自动>decltype方法显式实例化函数
- MSVC 编译器实例化函数模板的默认定义,即使存在专用化
- 如何使用模板模板参数显式实例化函数
- 实例化函数模板的编译问题
- Lua到c++,用于实体修改的实例化函数
- 当类型参数为空时,无法实例化函数模板
- 调用实例化函数时发生链接器错误
- 实例化函数的多个模板并在运行时选择的通用解决方案
- 使用不完整类型显式实例化函数模板
- 实例化函数模板时出错
- 禁止使用迭代器形参实例化函数模板