C++ lambda表达式的生命周期是多少?

66 c++ lambda c++11

(我已经阅读了C++中lambda派生的隐式仿函数的生命周期是什么?已经没有回答这个问题了.)

我理解C++ lambda语法只是用于创建具有调用操作符和某个状态的匿名类的实例的糖,并且我理解该状态的生存期要求(由您是否通过引用的值捕获来决定.)但是什么是lambda对象本身的生命周期?在以下示例中,std::function返回的实例是否有用?

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}
Run Code Online (Sandbox Code Playgroud)

如果是,它是如何工作的?这对我来说似乎有点太神奇了 - 我只能通过std::function复制整个实例来实现它的工作,根据我捕获的内容,这可能非常繁重 - 过去我std::function主要使用裸函数指针,复制它们是快.鉴于std::function类型擦除,它似乎也有问题.

Den*_*ose 61

如果用手动编织器代替lambda,它的生命周期正是如此:

struct lambda {
   lambda(int x) : x(x) { }
   int operator ()(int y) { return x + y; }

private:
   int x;
};

std::function<int(int)> meta_add(int x) {
   lambda add(x);
   return add;
}
Run Code Online (Sandbox Code Playgroud)

该对象将在meta_add函数本地创建,然后[在其entirty中,包括值x]移动到返回值中,然后本地实例将超出范围并正常销毁.但是,只要包含该std::function对象的对象执行,该函数返回的对象将保持有效.这多长时间显然取决于调用上下文.

  • 如果您不熟悉C++ 11之前函数对象的工作方式,那么您应该看看它们,因为lambda几乎不是函数对象的语法糖.一旦你理解了lambda具有与函数对象相同的值语义,因此它们的生命周期是相同的. (4认同)

def*_*ode 16

看起来你std::function比lambdas 更困惑.

std::function使用一种称为类型擦除的技术.这是一个快速飞过.

class Base
{
  virtual ~Base() {}
  virtual int call( float ) =0;
};

template< typename T>
class Eraser : public Base
{
public:
   Eraser( T t ) : m_t(t) { }
   virtual int call( float f ) override { return m_t(f); }
private:
   T m_t;
};

class Erased
{
public:
   template<typename T>
   Erased( T t ) : m_erased( new Eraser<T>(t) ) { }

   int do_call( float f )
   {
      return m_erased->call( f );
   }
private:
   Base* m_erased;
};
Run Code Online (Sandbox Code Playgroud)

你为什么要删除这个类型?不是我们想要的类型int (*)(float)吗?

类型擦除允许的内容Erased现在可以存储任何可调用的值int(float).

int boring( float f);
short interesting( double d );
struct Powerful
{
   int operator() ( float );
};

Erased e_boring( &boring );
Erased e_interesting( &interesting );
Erased e_powerful( Powerful() );
Erased e_useful( []( float f ) { return 42; } );
Run Code Online (Sandbox Code Playgroud)

  • 回想起来,我对std :: function感到困惑,因为我不知道它保留了很多东西的所有权.我假设将一个实例"包装"到std :: function后该实例左边的范围无效.lambdas将混淆带到头部的原因是因为std :: function基本上是传递它们的唯一方法(如果我有一个命名类型,我只返回一个命名类型的实例,而且这很明显) ,然后我不知道实例去了哪里. (3认同)
  • 这只是示例代码,缺少一些细节.它泄漏了内存,并且缺少对`std :: move`,`std :: forward`的调用.另外`std :: function`通常使用小对象优化来避免在`T`很小时使用堆. (2认同)

Mar*_*ork 11

这是:

[x](int y) { return x + y; };
Run Code Online (Sandbox Code Playgroud)

相当于:(或者也可以考虑)

struct MyLambda
{
    MyLambda(int x): x(x) {}
    int operator()(int y) const { return x + y; }
private:
    int x;
};
Run Code Online (Sandbox Code Playgroud)

所以你的对象正在返回一个看起来就像那样的对象.其中有一个定义良好的复制构造函数.因此,它可以从函数中正确复制似乎非常合理.

  • @Joe:你基本上描述了磨机类型擦除的运行,这正是它的工作原理. (2认同)

Dan*_*ien 6

在您发布的代码中:

std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}
Run Code Online (Sandbox Code Playgroud)

std::function<int(int)>函数返回的对象实际上保存了分配给局部变量的 lambda 函数对象的移动实例add

当您定义按值或按引用捕获的 C++11 lambda 时,C++ 编译器会自动生成唯一的函数类型,当调用 lambda 或将其分配给变量时会构造该函数类型的实例。为了说明这一点,您的 C++ 编译器可能会为 定义的 lambda 生成以下类类型[x](int y) { return x + y; }

class __lambda_373s27a
{
    int x;

public:
    __lambda_373s27a(int x_)
        : x(x_)
    {
    }

    int operator()(int y) const {
        return x + y;
    }
};
Run Code Online (Sandbox Code Playgroud)

那么,该meta_add函数本质上等价于:

std::function<int(int)> meta_add(int x) {
    __lambda_373s27a add = __lambda_373s27a(x);
    return add;
}
Run Code Online (Sandbox Code Playgroud)

编辑:顺便说一句,我不确定您是否知道这一点,但这是 C++11 中函数柯里化的一个示例。

  • 不,允许 `return` 语句隐式地将返回值视为右值,使其隐式可移动并消除对显式 `return std::move(...);` 的需要(这实际上阻止了 RVO/NRVO)使 `return std::move(...);` 成为反模式)。因此,由于“add”在“return”语句中被视为右值,因此 lambda 被移至“std::function&lt;&gt;”构造函数参数中。 (3认同)