【发布时间】:2014-02-03 05:11:14
【问题描述】:
出于说明目的,假设我要实现一个通用整数比较函数。我可以想到一些方法来定义/调用函数。
(A) 函数模板+函子
template <class Compare> void compare_int (int a, int b, const std::string& msg, Compare cmp_func)
{
if (cmp_func(a, b)) std::cout << "a is " << msg << " b" << std::endl;
else std::cout << "a is not " << msg << " b" << std::endl;
}
struct MyFunctor_LT {
bool operator() (int a, int b) {
return a<b;
}
};
这将是对该函数的几次调用:
MyFunctor_LT mflt;
MyFunctor_GT mfgt; //not necessary to show the implementation
compare_int (3, 5, "less than", mflt);
compare_int (3, 5, "greater than", mflt);
(B) 函数模板 + lambdas
我们会这样称呼compare_int:
compare_int (3, 5, "less than", [](int a, int b) {return a<b;});
compare_int (3, 5, "greater than", [](int a, int b) {return a>b;});
(C) 函数模板 + std::function
相同的模板实现,调用:
std::function<bool(int,int)> func_lt = [](int a, int b) {return a<b;}; //or a functor/function
std::function<bool(int,int)> func_gt = [](int a, int b) {return a>b;};
compare_int (3, 5, "less than", func_lt);
compare_int (3, 5, "greater than", func_gt);
(D) 原始“C 风格”指针
实施:
void compare_int (int a, int b, const std::string& msg, bool (*cmp_func) (int a, int b))
{
...
}
bool lt_func (int a, int b)
{
return a<b;
}
调用:
compare_int (10, 5, "less than", lt_func);
compare_int (10, 5, "greater than", gt_func);
在列出这些场景后,我们在每种情况下都有:
(A) 两个模板实例(两个不同的参数)将被编译并分配到内存中。
(B) 我想说两个模板实例也会被编译。每个 lambda 是一个不同的类。如果我错了,请纠正我。
(C) 只会编译一个模板实例,因为模板参数总是相同的:std::function<bool(int,int)>。
(D) 显然我们只有一个实例。
今天不用,对于这样一个幼稚的例子来说,它并没有什么不同。但是当使用数十个(或数百个)模板和众多仿函数时,编译时间和内存使用差异可能很大。
我们是否可以说在许多情况下(即,当使用太多具有相同签名的函子时)std::function(甚至函数指针)必须优先于模板+原始函子/lambdas?用std::function 包装你的函子或 lambda 可能非常方便。
我知道std::function(也是函数指针)引入了开销。值得吗?
编辑。我使用以下宏和一个非常常见的标准库函数模板(std::sort)做了一个非常简单的基准测试:
#define TEST(X) std::function<bool(int,int)> f##X = [] (int a, int b) {return (a^X)<(b+X);}; \
std::sort (v.begin(), v.end(), f##X);
#define TEST2(X) auto f##X = [] (int a, int b) {return (a^X)<(b^X);}; \
std::sort (v.begin(), v.end(), f##X);
#define TEST3(X) bool(*f##X)(int, int) = [] (int a, int b) {return (a^X)<(b^X);}; \
std::sort (v.begin(), v.end(), f##X);
关于生成的二进制文件大小的结果如下(GCC at -O3):
- 带有 1 个 TEST 宏实例的二进制文件:17009
- 1 TEST2 宏实例:9932
- 1 TEST3 宏实例:9820
- 50 个 TEST 宏实例:59918
- 50 个 TEST2 宏实例:94682
- 50 个 TEST3 宏实例:16857
即使我展示了数字,它也是一个比定量基准更定性的基准。正如我们所料,基于std::function 参数或函数指针的函数模板可以更好地扩展(就大小而言),因为创建的实例并不多。不过我没有测量运行时内存使用情况。
关于性能结果(向量大小为 1000000 个元素):
- 50 个 TEST 宏实例:5.75s
- 50 个 TEST2 宏实例:1.54 秒
- 50 个 TEST3 宏实例:3.20 秒
这是一个显着的区别,我们不能忽视std::function 引入的开销(至少如果我们的算法包含数百万次迭代)。
【问题讨论】:
-
如果是无状态的,可以将一个lambda转换为一个函数指针。只需使用一元魔法。
-
Lambda 是函子的语法糖; (A) 和 (B) 是相同的,不需要单独分析。
-
@Casey 我同意,但我不能 100% 确定两个不同的 lambda 是否意味着两个不同的模板参数。
-
您的
+X是sort上的 UB,因为它不再是一个订单。尝试(a^X)<(b^X)以获得每个X不同的明确定义的行为。另外,创建Test3,将其分配给无状态lambdas支持的bool(*)(int,int)。 -
你是对的,我什至没有想到比较功能。由于模板实例化,我只是对测量代码大小感兴趣。无论如何,我用新功能更新了结果。它们大致相同。我还为函数指针添加了结果 (TEST3)。我认为结论是直截了当的。在内存受限的系统上工作并将超过 10-15 个具有相同签名的不同仿函数/lambdas 传递给模板?然后使用函数指针。但为了获得最佳性能,请始终使用“原始”函子/lambdas。
标签: c++ templates c++11 lambda functor