在C++中,编译器将函数对象“内联”是什么意思?

20
在维基百科关于函数对象的文章中提到,当与for_each一起使用时,这些对象具有性能优势,因为编译器可以将它们“内联”。 在这个上下文中,我不太清楚这意味着什么...或者任何上下文,我很尴尬地说。感谢您的帮助!
4个回答

18
for_each模板的最后一个参数是一个函数对象函数对象是可以使用()运算符“调用”的东西(可能带有参数)。根据定义,有两种不同类型的函数对象:
  1. 普通非成员函数是函数对象。
  2. 重载了()运算符的类类型对象(所谓的函数对象)也是函数对象。

现在,如果你想将一个普通函数用作for_each的函数对象,它看起来会像下面这样:

inline void do_something(int &i) { /* do something */ }

int main() {
  int array[10];
  std::for_each(array, array + 10, &do_something);
}
在这种情况下,for_each模板使用[推导]参数<int *, void (*)(int &)>进行实例化。请注意,在此情况下,实际的函数对象值是作为函数参数传递的函数指针&do_something。从for_each函数的角度看,这是一个运行时值。由于它是一个运行时值,所以对函数对象的调用不能内联(就像一般情况下不可能通过函数指针内联任何调用一样)。
但是如果我们使用函数对象,代码可能如下所示。
struct do_something {
  void operator()(int &i) { /* do something */ }
}; 

int main() {
  int array[10];
  std::for_each(array, array + 10, do_something());
}
在这种情况下,for_each 模板被实例化为具有[推导]参数<int *, do_something>。从 for_each 中对函数对象的调用将被定向到 do_something::operator()。目标调用在编译时已知且不变。由于目标函数在编译时已知,因此可以轻松地进行内联。
当然,在后一种情况下,我们也将运行时值作为参数传递给 for_each。它是我们调用 for_each 时创建的 do_something 类的 [可能是“虚拟的”临时] 实例。但是,此运行时值对调用的目标没有影响(除非 operator () 是虚拟的),因此不会影响内联。

但是编译器可以获得::std::for_each的完整定义。对于聪明的编译器来说,它应该能够看到它被调用时带有一个特定的参数,这个参数恰好是一个声明为内联的函数的地址。编译器应该能够像类方法一样将函数调用内联。 - Omnifarious
2
@Omnifarious:在我的例子中,您可以使用具有void(int&)签名的不同函数调用for_each,并且编译器被强制使用相同的、唯一的for_each<int*,void(*)(int&)>实例化。也就是说,编译器必须每次都使用相同的for_each实例化,并对do_something函数执行间接调用。当然,聪明的编译器可以找到一种方式来悄悄地创建几个for_each实例,正如您所说,但在一般情况下,这将是一个相当可疑的事情。 - AnT stands with Russia
2
举个例子(忘记模板),当我在程序中有函数 void foo(int) 并多次调用它如 foo(1); foo(2); foo(3);,我通常期望编译器生成一个 foo 函数体并使用不同的参数 1、2 和 3 执行相同的函数体。如果我发现编译器生成了三个不同的 foo 函数体,并分别使用“内联”常量 1、2 和 3,那么我会感到不愉快。对于小的 foo 来说这没问题(如果之后被内联了),但对于较大的 foo 来说则是不可取的。你所描述的本质上是相同的事情。 - AnT stands with Russia
@Omnifarious: 是的,完全正确!将函数指针作为模版参数确实可行,且确实不需要额外的内联工作。但是在std::for_each的情况下,函数指针本身是函数参数(不是模板参数)。模板参数是函数指针类型 - AnT stands with Russia
关于评论 在我的示例中,您可以使用具有 void (int&) 签名的不同函数调用 for_each,编译器被强制使用唯一一个 for_each 实例,这与 ODR 有关吗? - user3882729
显示剩余3条评论

7

内联是编译器可以用函数本身的内容替换对函数的调用的过程。

这需要编译器在编译时知道函数的内容。

如果传递了函数指针,编译器通常无法执行此操作。


3

内联就是直接用函数体替换对该函数的每个调用。

这是针对小函数的优化,因为它减少了跳转到新函数然后返回的开销。


3
这意味着函数的定义(代码)可以被复制,从而避免函数调用(在某些系统上被认为是昂贵的)。可以将其视为宏替换。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接