何时使用函数对象而不是lambda表达式

29

在何种情况下创建一个函数对象比使用lambda表达式更合适?

我知道我的问题与何时使用lambda而不是函数对象的问题实质上是相反的,但我无法想象在实践中存在一种情况,其中函数对象会优先于lambda。 你有什么想法吗?


1
例如,std::function 基本上是一个高级的函数对象。当函数对象更加复杂时,与 lambda 表达式相比,函数对象尤其有用。 - Cornstalks
1
如果你想在多个地方重复使用它。通常 lambda 的吸引力(至少对我来说)在于它是就地定义的。但是,如果你需要在许多地方使用相同的比较器(例如),那么函数对象更有意义。 - Borgleader
1
有些人仍然无法使用C++11:我会说这是这样的情况之一。虽然不是一个好情况。 - ereOn
如果由于某种原因,您拥有一个不太短的比较器或谓词,它具有状态,并且您在多个位置使用它(具有不同的状态,因此想象一下多个函数对象的实例,每个实例都被多次使用)。这似乎是牵强的,但是这是可以想象的。此外,如果您需要它成为一个模板,并且您手头没有C++14。 - PeterT
7
调试进入一个函数对象可能比调试进入一个lambda函数更方便。 - user2249683
4个回答

43

一个 lambda 是一个函数对象(functor)- 它只是用更短的语法定义而已。

问题在于,这种语法有局限性。它并不总能让你以最高效和灵活的方式解决问题 - 或者根本无法解决。直到 C++14,operator() 甚至都不能是一个模板。

此外,lambda 只有一个 operator()。你无法提供多个重载来区分参数类型。

struct MyComparator
{
    bool operator()( int a, int b ) const {return a < b;}
    bool operator()( float a, float b ) const {return /*Some maths here*/;}
};

闭包对象参数的类型或值类别也可以传递,你也可以不定义特殊成员函数,包括构造函数和析构函数——如果一个函数对象应该负责资源,怎么办呢?

另一个问题是lambda表达式不能递归调用。当然,普通函数(包括运算符函数)可以。

此外,需要考虑lambda表达式在关联容器中作为比较器或在智能指针中作为删除器使用时不太方便:你不能直接将闭包类型作为模板参数传递,并且需要从另一个闭包对象构造容器成员(闭包类型没有默认构造函数!)。对于块作用域的map,这并不是太麻烦:

auto l = [val] (int a, int b) {return val*a < b;};
std::map<int, int, decltype(l)> map(l);

现在,如果你的map是一个数据成员会发生什么?在构造函数的初始化列表中需要什么模板参数、什么初始化器?你必须使用另一个静态数据成员——但由于你必须在类定义之外定义它,这可能会被认为很丑陋。

总而言之:Lambda表达式并不适用于更复杂的情况,因为它们不是为此而设计的。它们提供了一种简短和简洁的方式来创建简单的函数对象,用于相应地简单的情况。


1
inc(vote): 他们也不能有*this是一个&&的重载!而且谁不想在他们的函数对象上有rvalue重载呢? - Yakk - Adam Nevraumont
2
“lambdas(...)不能递归”这并不完全正确,您可以使lambda递归,只是需要预先知道其类型(例如通过将lambda存储在std :: function中)并在捕获列表中引用自身的引用。实用吗?不太可能,更不用说您无法像这样返回lambda(引用本地变量),但从技术上讲是可能的。 - Jiří Pospíšil
@JiříPospíšil 我为了简单起见而忽略了它,因为这是不切实际的。 - Columbo
1
@JiříPospíšil 感觉你的 lambda 不是递归的吗?缺少堆栈状态和自我引用吗?直接将 Y 组合器 应用于前额。(请注意,此递归 lambda 可以返回,且不使用类型抹消)。 - Yakk - Adam Nevraumont
Lambdas允许创建本地模板(多态lambda)。本地类可能不是模板,也不能包含模板成员。 - dyp
显示剩余4条评论

15

当我需要使用多个实例时,我会考虑使用函数对象而不是lambda表达式。

  • 当需要进行高级资源处理时。
  • 当需要作为类型引用函数对象时,例如将其作为模板参数传递。
  • (相关)当可以为类型(而不是实例)指定有意义且通常有用的名称时。
  • 当发现将逻辑拆分成子函数可以更清晰地编写时。在lambda表达式中,我们必须将所有内容编写到单个函数中。

7

我能想到两种情况:

  1. 当函数对象携带内部状态时,函数对象存在着非平凡的生命周期问题。它在使用之间保留了“某些东西”。

  2. 当你必须在各个地方使用相同的代码时,将其编写并保持为一个独立头文件中的函数对象可能是从维护角度考虑的好方法。


6

lambda表达式不能在未求值的上下文中使用。一个特别假设的例子来自于Shafik's的回答:

  1. In many cases it just useless since each lambda has a unique type, the hypothetical example given:

    template<typename T, typename U>
    void g(T, U, decltype([](T x, T y) { return x + y; }) func);
    
    g(1, 2, [](int x, int y) { return x + y; });
    

    The type of the lambda in the declaration and the call are different(by definition) and therefore this can not work.

因此,即使具有相同语法的 lambda 也不能替代另一个 lambda。在这种情况下,函数对象将起作用。

你刚刚创建了一个通用的lambda回调函数!太棒了! - sircodesalot

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