【问题标题】:When to use functors over lambdas何时在 lambda 上使用仿函数
【发布时间】:2015-01-31 02:12:04
【问题描述】:

是否存在过创建仿函数比使用 lambda 更有意义的情况?

我知道我的问题实际上是 when to use a lambda over a functor 的反面,但我想不出在实践中函子比 lambda 更首选的情况。对此有什么想法吗?

【问题讨论】:

  • 例如,std::function 基本上是一个高级仿函数。当函子更复杂时,它们比 lambdas 特别有用。
  • 如果你想在一堆地方重复使用它。通常 lambda 的吸引力(至少对我而言)是它已经到位。但是,如果您需要在很多地方(例如)使用相同的比较器,那么仿函数会更有意义。
  • 遗憾的是,有些人仍然无法使用 C++11:我想说这就是这样一种情况。不过不是很好。
  • 如果,由于某种原因,你有一个不是很短的比较器或谓词,它有一个状态并且你在多个位置使用(具有不同的状态,所以想象一个仿函数的多个实例,每个实例都使用了多次) .这似乎是人为的,但它是可以想象的。此外,如果您需要它作为模板并且手头没有 C++14。
  • 调试到仿函数 (is) 可能比调试到 lambda 更好

标签: c++ c++11 lambda functor


【解决方案1】:

lambda 是一个仿函数 - 只是用更短的语法定义。

问题是这种语法是有限的。它并不总是允许您以最有效和最灵活的方式解决问题 - 或者根本无法解决问题。在 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 的另一个问题是它们不能递归。当然,普通函数(包括算子函数)都可以。

还要考虑 lambdas 不方便用作关联容器的比较器或智能指针的删除器:您不能直接将闭包类型作为模板参数传递,并且需要从另一个闭包对象构造容器成员。 (闭包类型没有默认构造函数!)。对于块范围 map 来说,这并不太麻烦:

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

现在,如果您的map数据成员,会发生什么?什么模板参数,构造函数初始化列表中的什么初始化器?您必须使用另一个静态数据成员 - 但由于您必须在类定义之外定义它,这可以说是丑陋的。

总结:Lambda 不适用于更复杂的场景,因为它们不是为它们而设计的。它们提供了一种为相应简单的情况创建 simple 函数对象的简洁方法。

【讨论】:

  • inc(vote): 他们也不能有*this&amp;&amp; 重载!谁不想在他们的函数对象上重载右值?
  • "lambdas (...) cannot be recursive" 这不完全正确,你可以让 lambda 递归,只是它的类型需要提前知道(例如通过将 lambda 存储在 @ 987654331@) 并在捕获列表中引用自身。实际的?不是真的,更不用说你不能返回这样的 lambda(引用本地),但在技术上是可能的。
  • @JiříPospíšil 为了简单起见,我把它省略了,因为它不切实际。
  • 没有超载? Template magic to the rescue!
  • @JiříPospíšil 你觉得你的 lambda 是非递归的吗?您是否缺少堆栈状态和自引用?将Y combinator 直接涂抹在额头上。 (注意这个递归 lambda 可以返回,并且不使用类型擦除)
【解决方案2】:

我会考虑在 lambda 上使用仿函数

  • 需要多个实例。
  • 必须进行高级资源处理。
  • 需要将函子称为类型。例如,将其作为模板参数传递。
  • (相关)可以为 type(相对于 instance)提供一个有意义且通常有用的名称。
  • 发现将逻辑拆分为子功能时可以写得更简洁。在 lambda 中,我们必须将所有内容都写到一个函数中。

【讨论】:

    【解决方案3】:

    我可以想到两种情况:

    1. 当函子携带内部状态时,函子存在一个重要的生命周期问题。它在使用之间保留“一些东西”

    2. 当您必须在各处使用相同的代码时,从维护的角度来看,将其编写为函子并将其保留在自己的标头中可能是一个好主意

    【讨论】:

      【解决方案4】:

      lambda 不能是 used in an unevaluated context。一个特别做作的例子是从Shafik's偷来的答案:

      1. 在许多情况下它只是无用的,因为每个 lambda 都有一个唯一的类型,给出的假设示例:

        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; });
        

        声明和调用中的 lambda 类型是 不同(根据定义),因此这是行不通的。

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

      【讨论】:

      • 你是不是做了一个通用的 lambda 回调!太棒了!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-21
      • 2014-02-20
      • 2019-06-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多