lambda函数对象中的静态变量如何工作?

时间:2011-12-05 19:57:56

标签: c++ c++11 static lambda

lambda中使用的静态变量是否在使用lambda的函数调用中保留?或者是每个函数调用再次“创建”函数对象?

无用的例子:

#include <iostream>
#include <vector>
#include <algorithm>

using std::cout;

void some_function()
{
    std::vector<int> v = {0,1,2,3,4,5};
    std::for_each( v.begin(), v.end(),
         [](const int &i)
         {
             static int calls_to_cout = 0;
             cout << "cout has been called " << calls_to_cout << " times.\n"
                  << "\tCurrent int: " << i << "\n";
             ++calls_to_cout;
         } );
}

int main()
{
    some_function();
    some_function();
}

此程序的正确输出是什么? 如果lambda捕获局部变量,它是否依赖于事实? (它肯定会改变函数对象的底层实现,因此可能会产生影响)是否允许行为不一致?

我不是在寻找:“我的编译器输出......”,这是一个太新的功能,无法信任当前的实现恕我直言。我知道要求标准报价似乎很受欢迎,因为世界发现这样的事情存在,但我仍然希望有一个合适的来源。

6 个答案:

答案 0 :(得分:42)

tl; dr版本在底部。


§5.1.2 [expr.prim.lambda]

  

p1 lambda-expression
   lambda-introducer lambda-declarator opt compound-statement

     

p3 lambda-expression 的类型(也是闭包对象的类型)是一个唯一的,未命名的非联盟类类型 - 称为闭包类型 - 其属性如下所述。此类类型不是聚合(8.5.1)。 闭包类型在包含相应 lambda-expression 的最小块作用域,类作用域或命名空间作用域中声明。 (我的注释:函数具有块范围。

     

p5 lambda-expression 闭包类型有一个公共inline函数调用运算符[...]

     

p7 lambda-expression 复合语句产生函数调用运算符的 function-body (8.4)[.. ]

由于复合语句直接作为函数调用操作符的主体,并且闭包类型在最小(最里面)范围内定义,因此与编写以下内容相同:

void some_function()
{
    struct /*unnamed unique*/{
      inline void operator()(int const& i) const{
        static int calls_to_cout = 0;
        cout << "cout has been called " << calls_to_cout << " times.\n"
             << "\tCurrent int: " << i << "\n";
        ++calls_to_cout;

      }
    } lambda;
    std::vector<int> v = {0,1,2,3,4,5};
    std::for_each( v.begin(), v.end(), lambda);
}

哪些是合法的C ++,允许函数具有static局部变量。

§3.7.1 [basic.stc.static]

  

p1所有没有动态存储持续时间,没有线程存储持续时间且不是本地的变量都具有静态存储持续时间。 这些实体的存储应在程序期间持续

     

p3关键字static可用于声明具有静态存储持续时间的局部变量。 [...]

§6.7 [stmt.dcl] p4
(这涉及在块范围内使用静态存储持续时间初始化变量。)

  

[...]否则这个变量在第一次控制通过其声明时被初始化; [...]


重申:

  • lambda表达式的类型在最里面的范围内创建。
  • 对于每个函数调用都重新创建 (这是没有意义的,因为封闭的函数体将如上所述)。
  • 它遵循(几乎)普通类/结构的所有规则(只是关于this的一些东西是不同的),因为它非联合类类型。

现在我们已经确保对于每个函数调用,闭包类型是相同的,我们可以有把握地说静态局部变量也是一样的;它是在第一次调用函数调用操作符时初始化的,直到程序结束。

答案 1 :(得分:12)

静态变量应该像在函数体中一样。但是没有理由使用一个,因为lambda对象可以有成员变量。

在下文中,calls_to_cout由value捕获,这为lambda提供了一个具有相同名称的成员变量,初始化为当前值calls_to_cout。此成员变量在调用之间保留其值,但对于lambda对象是本地的,因此lambda的任何副本都将获得自己的calls_to_cout成员变量,而不是共享一个静态变量。这样更安全,更好。

(因为lambdas默认是const,这个lambda修改calls_to_cout,它必须声明为可变。)

void some_function()
{
    vector<int> v = {0,1,2,3,4,5};
    int calls_to_cout = 0;
    for_each(v.begin(), v.end(),[calls_to_cout](const int &i) mutable
    {
        cout << "cout has been called " << calls_to_cout << " times.\n"
          << "\tCurrent int: " << i << "\n";
        ++calls_to_cout;
    });
}

如果你想要在lambda的实例之间共享一个变量,你最好还是使用捕获。只需捕获对变量的某种引用。例如,这是一个函数,它返回一对共享对单个变量的引用的函数,并且每个函数在被调用时对该共享变量执行自己的操作。

std::tuple<std::function<int()>,std::function<void()>>
make_incr_reset_pair() {
    std::shared_ptr<int> i = std::make_shared<int>(0);
    return std::make_tuple(
      [=]() { return ++*i; },
      [=]() { *i = 0; });
}

int main() {
    std::function<int()> increment;
    std::function<void()> reset;
    std::tie(increment,reset) = make_incr_reset_pair();

    std::cout << increment() << '\n';
    std::cout << increment() << '\n';
    std::cout << increment() << '\n';
    reset();
    std::cout << increment() << '\n';

答案 2 :(得分:6)

可以在捕获中构建静态: -

auto v = vector<int>(99);
generate(v.begin(), v.end(), [x = int(1)] () mutable { return x++; });

lambda可以由另一个lambda

制作
auto inc = [y=int(1)] () mutable { 
    ++y; // has to be separate, it doesn't like ++y inside the []
    return [y, x = int(1)] () mutable { return y+x++; }; 
};
generate(v.begin(), v.end(), inc());

此处,只要公司持续时间更长,也可以通过引用捕获y。

答案 3 :(得分:2)

我没有最终标准的副本,而draft似乎没有明确解决问题(请参阅第5.1.2节,从PDF的第87页开始)。但它确实说lambda表达式求值为闭包类型的单个对象,可以重复调用。既然如此,我相信标准要求静态变量只被初始化一次,就好像你已经写出了类operator()和变量捕获一样。

但正如你所说,这是一个新功能;至少就目前而言,无论标准是什么,你都会遇到任何实施问题。无论如何,明确捕获封闭范围中的变量是更好的方式。

答案 4 :(得分:1)

通过lambda使用状态有两种方法。

  1. 在lambda中将变量定义为static: 在lambda调用和lambda实例上具有持久性。
  2. 在lambda捕获中定义变量并将lambda标记为mutable:该变量在lambda调用中是持久的,但会在每个lambda实例化处重置

以下代码说明了区别:

void foo() {
   auto f = [k=int(1)]() mutable { cout << k++ << "\n";}; // define k in the capture
   f();
   f();
}

void bar() {
   auto f = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
   f();
   f();
}

void test() {
   foo();
   foo();  // k is reset every time the lambda is created
   bar();
   bar();  // k is persistent through lambda instantiations
   return 0;
}

答案 5 :(得分:0)

简短回答:在lambda内部声明的静态变量与封闭范围内的函数静态变量相同(通过引用)。

在这种情况下,即使lambda对象返回两次,值仍然存在:

auto make_sum()
{
    static int sum = 0;
    static int count = 0;

    //Wrong, since these do not have static duration, they are implicitly captured
    //return [&sum, &count](const int&i){
    return [](const int&i){
        sum += i;
        ++count;

        cout << "sum: "<< sum << " count: " << count << endl;
    };
}

int main(int argc, const char * argv[]) {
    vector<int> v = {0,1,1,2,3,5,8,13};

    for_each(v.begin(), v.end(), make_sum());

    for_each(v.begin(), v.end(), make_sum());

    return 0;
}

VS

auto make_sum()
{
    return [](const int&i){
        //Now they are inside the lambda
        static int sum = 0;
        static int count = 0;

        sum += i;
        ++count;

        cout << "sum: "<< sum << " count: " << count << endl;
    };
}

int main(int argc, const char * argv[]) {
    vector<int> v = {0,1,1,2,3,5,8,13};

    for_each(v.begin(), v.end(), make_sum());

    for_each(v.begin(), v.end(), make_sum());

    return 0;
}

两者都给出相同的输出:

sum: 0 count: 1
sum: 1 count: 2
sum: 2 count: 3
sum: 4 count: 4
sum: 7 count: 5
sum: 12 count: 6
sum: 20 count: 7
sum: 33 count: 8
sum: 33 count: 9
sum: 34 count: 10
sum: 35 count: 11
sum: 37 count: 12
sum: 40 count: 13
sum: 45 count: 14
sum: 53 count: 15
sum: 66 count: 16