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

在使用lambda的函数的调用中保留的lambda中使用了静态variables? 或者是函数对象“创build”每个函数调用?

无用的例子:

#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捕获局部variables或不? (它肯定会改变函数对象的底层实现,所以它可能会有影响)这是一个允许的行为不一致吗?

我不是在寻找:“我的编译器输出…”,这是一个新function,以信任当前实施恕我直言。 我知道要求标准报价似乎是受欢迎的,因为世界上发现了这样的事情存在,但是,仍然,我想一个体面的来源。

tl; dr版本在底部。


§5.1.2 [expr.prim.lambda]

p1 lambdaexpression式
lambda-introducer lambda-declarator opt复合语句

p3 lambdaexpression式 (也是闭包对象的types)的types是一个唯一的,未命名的非联合类types – 称为闭包types – 其属性在下面描述。 这个类的types不是一个聚合(8.5.1)。 闭包types在包含相应lambdaexpression式的最小块作用域,类作用域或命名空间作用域中声明 。 ( 我的注释:函数有一个块范围。

p5 lambdaexpression式闭包types有一个公共inline函数调用操作符[…]

p7 lambdaexpression式复合语句产生函数调用操作符[…]的函数体 (8.4)

由于复合语句直接作为函数调用操作符的主体,闭包types定义在最小的(最内层)范围内,所以与下面的写法相同:

 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局部variables。

§3.7.1 [basic.stc.static]

p1所有没有dynamic存储时间的variables,没有线程存储时间,并且不是本地的,有静态存储时间。 这些实体的存储应在程序期间保存

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

§6.7 [stmt.dcl] p4
(这在块范围内处理具有静态存储持续时间的variables的初始化。)

[…]否则这样的variables在控制首次通过声明时被初始化; […]


重申:

  • lambdaexpression式的types是在最内层的范围内创build的。
  • 不会为每个函数调用重新创build(这是没有道理的,因为封闭的函数体将如我上面的示例)。
  • 它遵循(几乎)正常类/结构的所有规则(只是关于this一些东西是不同的),因为它一个非联合类types。

现在我们已经确定,对于每个函数调用,闭包types是相同的,我们可以放心地说,静态局部variables也是一样的; 它在第一次调用函数调用操作符时被初始化,并且一直存在直到程序结束。

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

在下面, calls_to_cout被value捕获,它给lambda一个同名的成员variables,初始化为calls_to_cout的当前值。 这个成员variables在调用中保留了它的值,但是是lambda对象的局部variables,所以lambda的任何副本都将获得它们自己的call_to_cout成员variables,而不是全部共享一个静态variables。 这样更安全,更好。

(因为lambda的默认是const,这个lambda修改了calls_to_cout所以它必须声明为mutable。)

 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的实例之间共享一个variables,那么使用捕获的效果还是比较好的。 只是捕获一些对variables的引用。 例如,这里有一个函数返回一对函数,它们共享一个variables的引用,每个函数在调用时对该共享variables执行自己的操作。

 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'; 

我没有最终标准的副本, 草案似乎没有明确地解决这个问题(参见PDF第87页的第5.1.2节)。 但是它确实表示一个lambdaexpression式计算为一个闭包types的单个对象,可能会被重复调用。 既然如此,我相信这个标准要求静态variables只被初始化一次,就像你写出了类operator()和variables捕获一样。

但正如你所说,这是一个新的特点; 至less现在你不pipe你的实现做什么,不pipe标准说什么。 无论如何,明确地捕获封闭范围中的variables是更好的风格。

捕获中可以构build一个静态的:

 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也可以通过引用被捕获。

简短的回答:在一个lambda函数内部声明的静态variables与封闭范围中的函数静态variables的工作方式是一样的(通过引用)。

在这种情况下,即使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