在 C++11 中,什么时候应该按值捕获 lambda 表达式的绑定变量?
我有一个 Visual Studio 2010 C++ 程序,其主要功能是:
vector<double> v(10);
double start = 0.0; double increment = 10.0;
auto f = [&start, increment]() { return start += increment; };
generate(v.begin(), v.end(), f);
for(auto it = v.cbegin(); it != v.cend(); ++it) { cout << *it << ", "; }
cout << endl << "Changing vars to try again..." << endl;
start = 15; increment = -1.5;
generate(v.begin(), v.end(), f);
for(auto it = v.cbegin(); it != v.cend(); ++it) { cout << *it << ", "; }
return 0;
当我在 MS Visual Studio 中编译该程序时,第一个生成按照我的预期进行,结果是“10, 20, ... 100,”。第二个没有; lambda“看到”了 start
中的变化,但看不到 increment
中的变化,所以我得到“25, 35, ... 115,”。
Visual C++ 编译器在声明表达式时(而不是调用表达式时)将 lambda 表达式绑定到其捕获的变量。 ...[T]稍后在程序中重新分配[通过值捕获的变量]不会影响表达式的结果。
所以我的问题是:这是符合标准的 C++11 行为,还是 Microsoft 自己的古怪实现?额外奖励:如果这是标准行为,为什么标准是这样写的?它与强制函数式编程的引用透明度有关吗?
I have a Visual Studio 2010 C++ program, the main function of which is:
vector<double> v(10);
double start = 0.0; double increment = 10.0;
auto f = [&start, increment]() { return start += increment; };
generate(v.begin(), v.end(), f);
for(auto it = v.cbegin(); it != v.cend(); ++it) { cout << *it << ", "; }
cout << endl << "Changing vars to try again..." << endl;
start = 15; increment = -1.5;
generate(v.begin(), v.end(), f);
for(auto it = v.cbegin(); it != v.cend(); ++it) { cout << *it << ", "; }
return 0;
When I compile this in MS Visual Studio, the first generate does what I expected, resulting in "10, 20, ... 100, ". The second does not; the lambda "sees" the change in start
but not the change in increment
, so I get "25, 35, ... 115, ".
MSDN explains that
The Visual C++ compiler binds a lambda expression to its captured variables when the expression is declared instead of when the expression is called. ... [T]he reassignment of [a variable captured by value] later in the program does not affect the result of the expression.
So my question is: is this standards-compliant C++11 behavior, or is it Microsoft's own eccentric implementation? Bonus: if it is standard behavior, why was the standard written that way? Does it have to do with enforcing referential transparency for functional programming?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
对于 lambda 表达式,绑定变量在声明时被捕获。
这个示例会说得非常清楚:https://ideone.com/Ly38P
很明显,捕获必须发生在调用之前,因为被捕获的变量在以后甚至不再存在(不在范围内,也不在生命周期中)。
With a lambda expression, the bound variables are captured at the time of declaration.
This sample will make it very clear: https://ideone.com/Ly38P
It is clear that the capture must be happening before the invocation, since the variables being captured don't even exist (not in scope, neither in lifetime) anymore at a later time.
它在创建时绑定。考虑一下:
当函数被调用时,这里没有
a
——编译器无法返回并更新它。如果您通过引用传递a
,则会出现未定义的行为。但如果你按值传递,任何理性的程序员都会期望它能工作。It's bound at creation time. Consider:
There's no
a
here when the function is called -- there'd be no way for the compiler to go back and update it. If you passa
by reference, then you have undefined behavior. But if you pass by value any reasonable programmer would expect this to work.我认为您将捕获机制与变量传递机制混淆了。即使它们表面上有些相似,它们也不是同一件事。如果您需要 lambda 表达式中变量的当前值,请通过引用捕获它(当然,该引用在声明 lambda 时绑定到特定变量)。
当您“捕获”变量时,您正在创建非常类似于闭包的东西。并且闭包始终是静态范围的(即“捕获”发生在声明点)。熟悉 lambda 表达式概念的人会发现 C++ 的 lambda 表达式非常奇怪且令人困惑,否则的话。在某种编程语言中添加一个与其他编程语言中的相同功能在某些方面有所不同的全新功能将使 C++ 比现在更加混乱和难以理解。此外,C++ 中的其他所有内容都是静态作用域的,因此添加一些动态作用域元素也会因为这个原因而非常奇怪。
最后,如果捕获总是通过引用发生,那么这意味着 lambda 仅在堆栈帧有效时才有效。要么你必须向 C++ 添加垃圾收集的堆栈帧(这对性能造成巨大影响,并且依赖堆栈很大程度上连续的人们会大喊大叫),要么你最终会创建另一个功能,在该功能中很容易毁掉你的功能。不小心踩了火箭筒,因为 lambda 表达式引用的堆栈帧会超出范围,并且您基本上会创建很多看不见的机会来通过引用返回局部变量。
I think you are confusing the mechanism of capture with the mechanism of variable passing. They are not the same thing even if they bear some superficial resemblance to one another. If you need the current value of a variable inside a lambda expression, capture it by reference (though, of course, that reference is bound to a particular variable at the point the lambda is declared).
When you 'capture' a variable, you are creating something very like a closure. And closures are always statically scoped (i.e. the 'capture' happens at the point of declaration). People familiar with the concept of a lambda expression would find C++'s lambda expressions highly strange and confusing if it were otherwise. Adding a brand new feature to a programming language that is different from the same feature in other programming languages in some significant way would make C++ even more confusing and difficult to understand than it already is. Also, everything else in C++ is statically scoped, so adding some element of dynamic scoping would be very strange for that reason as well.
Lastly, if capture always happened by reference, then that would mean a lambda would only be valid as long as the stack frame was valid. Either you would have to add garbage collected stack frames to C++ (with a huge performance hit and much screaming from people who are depending on the stack being largely contiguous) or you would end up creating yet another feature where it was trivially easy to blow your foot off with a bazooka by accident as the stack frame referenced by a lambda expression would go out of scope and you'd basically be creating a lot of invisible opportunities to return local variables by reference.
是的,它必须在该点按值捕获,否则您可能会尝试捕获实际调用 lambda/函数时不再存在的变量(例如通过引用)。
该标准支持通过值和引用捕获来解决两种可能的用例。如果您告诉编译器按值捕获,则会在创建 lambda 时捕获该值。如果您要求通过引用捕获,它将捕获对变量的引用,然后在调用 lambda 时使用该变量(当然要求引用的变量在调用时必须仍然存在)已拨打电话)。
Yes, it has to capture by value at the point because otherwise you could attempt to capture a variable (by reference for example) that no longer exists when the lambda/function is actually called.
The standard supports capturing both by value AND by reference to address both possible use cases. If you tell the compiler to capture by value it's captured at the point the lambda is created. If you ask to capture by reference, it will capture a reference to the variable which will then be used at the point the lambda is called (requiring of course that the referenced variable must still exist at the point the call is made).