使用 std::function 或转发引用作为高阶函数的通用可调用对象输入参数?
using a std::function or a forwarding reference as general-purpose callable object input parameter of a higher-order function?
我想知道编写高阶函数的主要区别、优点和缺点,将 std::function
或 转发引用 作为输入参数,例如template<typename F> void hof(F&& fun);
。
显然,前者比后者更严格,因为它指定了输入可调用对象必须符合的函数类型。
std::function
通常会有显着的 运行 时间开销。通过 template
参数传递通用可调用对象避免了 std::function
的间接成本,并且 允许编译器积极优化 .
我为 lambda 递归写了一些简单的基准测试 (Y-combinator vs std::function
) 在 the end of this article。 std::function
生成的程序集总是比非多态 Y 组合器实现多至少 3.5 倍。这是一个不错的 示例,它显示了 std::function
如何比 template
参数更昂贵。
我建议尝试 gcc.godbolt.org 以了解两种技术之间的组装差异。
这是一个例子I just came up with:
#if defined(STDFN)
void pass_by_stdfn(std::function<void()> f)
{
f();
}
#else
template <typename TF>
void pass_by_template(TF&& f)
{
f();
}
#endif
volatile int state = 0;
int main()
{
#if defined(STDFN)
pass_by_stdfn([i = 10]{ state = i; });
#else
pass_by_template([i = 10]{ state = i; });
#endif
}
With STDFN
未定义,生成的程序集为:
main:
mov DWORD PTR state[rip], 10
xor eax, eax
ret
state:
.zero 4
使用STDFN
定义,生成的程序集长48行。
std::function
有很多优点,但也有一系列必须考虑的缺点。
例如:
可调用对象应该是可复制构造的。
换句话说,这编译:
#include <functional>
#include <utility>
#include <memory>
template<typename F>
void func(F &&f) { std::forward<F>(f)(); }
int main() {
func([ptr = std::make_unique<int>()](){});
}
但这不是:
#include <functional>
#include <utility>
#include <memory>
void func(std::function<void(void)> f) { f(); }
int main() {
func([ptr = std::make_unique<int>()](){});
}
尽管(强调我的):
Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f's target is an object holding only a pointer or reference to an object and a member function pointer.
您不能保证在可以避免的情况下不会在动态存储上进行分配,并且在所有其他情况下您肯定会进行分配。
当你构造一个std::function
时,构造函数可能会抛出一个bad_alloc
.
...也许我们可以继续,但这不值得,你明白了。
std::function
是非常有用的工具,但是当您需要 时,您应该使用它们。
例如,如果您计划将 函数 存储在某处,您最终可能会使用 std::function
。另一方面,如果您打算接受一个可调用对象并即时调用它,您可能不会使用 std::function
.
这些只是几个例子,不幸的是,黄金法则并不存在。
我想知道编写高阶函数的主要区别、优点和缺点,将 std::function
或 转发引用 作为输入参数,例如template<typename F> void hof(F&& fun);
。
显然,前者比后者更严格,因为它指定了输入可调用对象必须符合的函数类型。
std::function
通常会有显着的 运行 时间开销。通过 template
参数传递通用可调用对象避免了 std::function
的间接成本,并且 允许编译器积极优化 .
我为 lambda 递归写了一些简单的基准测试 (Y-combinator vs std::function
) 在 the end of this article。 std::function
生成的程序集总是比非多态 Y 组合器实现多至少 3.5 倍。这是一个不错的 示例,它显示了 std::function
如何比 template
参数更昂贵。
我建议尝试 gcc.godbolt.org 以了解两种技术之间的组装差异。
这是一个例子I just came up with:
#if defined(STDFN)
void pass_by_stdfn(std::function<void()> f)
{
f();
}
#else
template <typename TF>
void pass_by_template(TF&& f)
{
f();
}
#endif
volatile int state = 0;
int main()
{
#if defined(STDFN)
pass_by_stdfn([i = 10]{ state = i; });
#else
pass_by_template([i = 10]{ state = i; });
#endif
}
With STDFN
未定义,生成的程序集为:
main:
mov DWORD PTR state[rip], 10
xor eax, eax
ret
state:
.zero 4
使用STDFN
定义,生成的程序集长48行。
std::function
有很多优点,但也有一系列必须考虑的缺点。
例如:
可调用对象应该是可复制构造的。
换句话说,这编译:#include <functional> #include <utility> #include <memory> template<typename F> void func(F &&f) { std::forward<F>(f)(); } int main() { func([ptr = std::make_unique<int>()](){}); }
但这不是:
#include <functional> #include <utility> #include <memory> void func(std::function<void(void)> f) { f(); } int main() { func([ptr = std::make_unique<int>()](){}); }
尽管(强调我的):
Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f's target is an object holding only a pointer or reference to an object and a member function pointer.
您不能保证在可以避免的情况下不会在动态存储上进行分配,并且在所有其他情况下您肯定会进行分配。
当你构造一个
std::function
时,构造函数可能会抛出一个bad_alloc
....也许我们可以继续,但这不值得,你明白了。
std::function
是非常有用的工具,但是当您需要 时,您应该使用它们。
例如,如果您计划将 函数 存储在某处,您最终可能会使用 std::function
。另一方面,如果您打算接受一个可调用对象并即时调用它,您可能不会使用 std::function
.
这些只是几个例子,不幸的是,黄金法则并不存在。