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

What is the lifetime of a C++ lambda expression?

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

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

1
2
3
4
std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

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


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

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
};

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

类型擦除允许的是Erased现在可以存储任何可调用的值,如int(float)

1
2
3
4
5
6
7
8
9
10
11
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; } );


这是:

1
[x](int y) { return x + y; };

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

1
2
3
4
5
6
7
struct MyLambda
{
    MyLambda(int x): x(x) {}
    int operator()(int y) const { return x + y; }
private:
    int x;
};

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


在您发布的代码中:

1
2
3
4
std::function<int(int)> meta_add(int x) {
    auto add = [x](int y) { return x + y; };
    return add;
}

函数返回的std::function对象实际上包含已分配给局部变量add的lambda函数对象的移动实例。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class __lambda_373s27a
{
    int x;

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

    int operator()(int y) const {
        return x + y;
    }
};

然后,meta_add函数基本上等同于:

1
2
3
4
std::function<int(int)> meta_add(int x) {
    __lambda_373s27a add = __lambda_373s27a(x);
    return add;
}

编辑:顺便说一句,我不知道你是否知道这一点,但这是C ++ 11中函数currying的一个例子。