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