这个工作的关键原因是for_each()实际上没有假设 成为一个函数的第三个参数。 它只是假设它的第三个 论证是可以的 用适当的参数调用。一个 适当定义的对象也适用 作为 - 通常比 - 更好 功能。例如,它更容易 内联应用程序运算符 类比内联函数传递 作为函数的指针。 因此,通常是功能对象 比普通人执行得更快 功能。一个类的对象 应用程序运营商(第11.9节)是 称为函数式对象,a 仿函数,或者只是一个函数对象。
[Stroustrup,C ++第3版,18.4-最后一段]
我一直以为是运营商 ()调用就像函数调用一样 在运行时。它有什么不同 正常的函数调用?
为什么更容易内联 应用程序运算符比正常 功能
它们如何比功能更快 调用
答案 0 :(得分:10)
通常,仿函数传递给模板函数 - 如果你这样做,那么你传递一个“真实”函数(即一个函数指针)或一个函子(无关紧要)即一个重载operator()
的类。本质上,它们都有一个函数调用操作符,因此是有效的模板参数,编译器可以为其实例化for_each
模板。 这意味着for_each
要么通过传递的仿函数的特定类型进行实例化,要么传递函数指针的特定类型。正是在这种专业化中,仿函数可以胜过函数指针。
毕竟,如果你传递一个函数指针,那么参数的编译类型就是 - 函数指针。如果for_each
本身没有内联,那么这个特定的for_each
实例被编译为调用一个不透明的函数指针 - 毕竟,编译器如何内联一个函数指针?它只知道它的类型,而不是实际传递了该类型的函数 - 至少,除非它在优化时可以使用非本地信息,这是很难做到的。
但是,如果您传递仿函数,则该仿函数的编译时类型用于实例化for_each
模板。在这样做时,您可能正在传递一个简单的非虚拟类,只有一个适当的operator()
实现。因此,当编译器遇到对operator()
的调用时,它确切地知道实现了哪个实现 - 该函数的唯一实现 - 现在它可以内联。
如果您的仿函数使用虚拟方法,潜在的优势就会消失。当然,一个仿函数是一个类,你可以用它来做各种其他低效的事情。但对于基本情况,这就是为什么编译器更容易优化&内联函数调用而不是函数指针调用。
无法内联函数指针,因为编译for_each
时编译器只有类型
功能而不是功能的标识。相比之下,仿函数可以内联,因为即使编译器只具有仿函数类型,类型通常也足以唯一地标识仿函数的operator()
方法。
答案 1 :(得分:4)
考虑以下两个模板实例:
std::for_each<class std::vector<int>::const_iterator, class Functor>(...)
和
std::for_each<class std::vector<int>::const_iterator, void(*)(int)>(...)
因为1st是为每种类型的函数对象定制的,并且因为operator()
通常是内联定义的,所以编译器可以自行决定选择内联调用。
在第二种情况下,编译器将为同一签名的所有函数实例化模板一次,因此,它不能轻松内联调用。
现在,智能编译器可能能够确定在编译时调用哪个函数,尤其是在这样的场景中:
std::for_each(v.begin(), v.end(), &foo);
并且仍然通过生成自定义实例而不是前面提到的单个泛型实例来内联函数。
答案 2 :(得分:0)
我一直认为operator()调用就像运行时的函数调用一样。它与普通函数调用有何不同?
我的猜测不是很多。有关此问题的证据,请查看编译器的汇编输出。假设具有相同的优化级别,它可能几乎相同。 (附加细节表明this
指针必须通过。)
为什么内联应用程序操作符比正常函数更容易?
引用你引用的模糊:
例如,内联类的应用程序操作符比内联作为函数指针传递的函数更容易。
我不是编译人员,但我读到这个:如果函数是通过函数指针调用的,那么编译器猜测存储在该函数指针中的地址是否会在运行时改变是一个难题,因此用函数体替换call
指令是不安全的;想到这一点,函数本体在编译时不一定是已知的。
它们如何比函数调用更快?
在许多情况下,我希望你不会注意到任何差异。但是,鉴于您的引用论证编译器可以自由地进行更多内联,这可以产生更好的代码locality和更少的branches。如果频繁调用代码,这将产生显着的加速。