rub*_*nvb 54 c++ lambda static c++11
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();
}
Run Code Online (Sandbox Code Playgroud)
这个程序的正确输出是什么?如果lambda捕获局部变量,它是否依赖于事实?(它肯定会改变函数对象的底层实现,因此可能会产生影响)是否允许行为不一致?
我不是在寻找:"我的编译器输出......",这是一个太新的功能,无法信任当前的实现恕我直言.我知道要求标准报价似乎很受欢迎,因为世界发现这样的事情存在,但我仍然想要一个不错的来源.
Xeo*_*Xeo 48
tl; dr版本在底部.
§5.1.2 [expr.prim.lambda]
p1 lambda-expression:
lambda-introducer lambda-declarator opt compound-statementp3 lambda-expression的类型(也是闭包对象的类型)是一个唯一的,未命名的nonunion类类型 - 称为闭包类型 - 其属性如下所述.此类类型不是聚合(8.5.1).闭包类型在包含相应lambda表达式的最小块作用域,类作用域或命名空间作用域中声明.(我的注释:函数有一个块范围.)
P5上的闭合类型用于λ-表达具有一个公共
inline
函数调用操作[...]p7 lambda-expression的复合语句产生函数调用运算符的函数体(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);
}
Run Code Online (Sandbox Code Playgroud)
哪个是合法的C++,允许函数有static
局部变量.
§3.7.1 [basic.stc.static]
p1所有没有动态存储持续时间,没有线程存储持续时间且不是本地的变量都具有静态存储持续时间.这些实体的存储应持续该计划的持续时间.
p3关键字
static
可用于声明具有静态存储持续时间的局部变量.[...]
§6.7 [stmt.dcl] p4
(这涉及在块范围内使用静态存储持续时间初始化变量.)
[...]否则这个变量在第一次控制通过其声明时被初始化; [...]
重申:
this
是不同的),因为它是一个非联合类类型.现在我们已经确保对于每个函数调用,闭包类型是相同的,我们可以有把握地说静态局部变量也是一样的; 它是在第一次调用函数调用操作符时初始化的,直到程序结束.
bam*_*s53 14
静态变量应该像在函数体中一样.但是没有理由使用一个,因为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;
});
}
Run Code Online (Sandbox Code Playgroud)
如果你也想成为拉姆达的实例之间共享的单一变量你还是最好使用捕捉.只需捕获对变量的某种引用.例如,这是一个函数,它返回一对共享对单个变量的引用的函数,并且每个函数在调用时对该共享变量执行自己的操作.
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';
Run Code Online (Sandbox Code Playgroud)
可以在捕获中构建静态: -
auto v = vector<int>(99);
generate(v.begin(), v.end(), [x = int(1)] () mutable { return x++; });
Run Code Online (Sandbox Code Playgroud)
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());
Run Code Online (Sandbox Code Playgroud)
在此,只要inc持续时间更长,也可以通过引用捕获y.
有两种方法可以将状态与 lambda 一起使用。
static
:变量在 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;
}
Run Code Online (Sandbox Code Playgroud)