我有一个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行为,还是微软自己的古怪实现?额外奖励:如果是标准行为,为什么标准是这样编写的?是否与强制执行函数式编程的参照透明度有关?
答案 0 :(得分:15)
使用lambda表达式,在声明时捕获绑定变量。
此示例将非常明确: https://ideone.com/Ly38P
std::function<int()> dowork()
{
int answer = 42;
auto lambda = [answer] () { return answer; };
// can do what we want
answer = 666;
return lambda;
}
int main()
{
auto ll = dowork();
return ll(); // 42
}
很明显,捕获必须在调用之前发生,因为捕获的变量在以后不再存在(不在范围内,也不在生命周期内)。
答案 1 :(得分:6)
它在创作时受到约束。考虑:
#include <functional>
#include <iostream>
std::function<int(int)> foo;
void sub()
{
int a = 42;
foo = [a](int x) -> int { return x + a; };
}
int main()
{
sub();
int abc = 54;
abc = foo(abc); // Note a no longer exists here... but it was captured by
// value, so the caller shouldn't have to care here...
std::cout << abc; //96
}
调用函数时,这里没有a
- 编译器无法返回并更新它。如果您通过引用传递a
,那么您有未定义的行为。但是,如果你通过价值传递,任何合理的程序员都希望这能够发挥作用。
答案 2 :(得分:3)
我认为你将捕获机制与变量传递机制混淆起来。即使它们彼此有一些肤浅的相似之处,它们也不是一回事。如果您需要lambda表达式中变量的当前值,请通过引用捕获它(当然,该引用在声明lambda的点处绑定到特定变量)。
当你'捕获'变量时,你正在创建一个非常像闭包的东西。并且闭包始终是静态范围的(即'捕获'发生在声明点)。熟悉lambda表达式概念的人会发现C ++的lambda表达式非常奇怪,如果不是这样的话会让人感到困惑。将一种全新的特性添加到与其他编程语言中的相同特征不同的编程语言中会以某种显着的方式增加,这将使C ++比现在更加混乱和难以理解。此外,C ++中的其他所有内容都是静态范围的,因此添加动态范围的某些元素也会非常奇怪。
最后,如果捕获总是通过引用发生,那么这意味着只要堆栈帧有效,lambda才有效。要么你必须将垃圾收集的堆栈帧添加到C ++(具有巨大的性能影响,并且大多数人依赖堆栈在很大程度上是连续的尖叫),或者你最终会创建另一个功能,其中很容易让你的由于lambda表达式引用的堆栈框架超出了范围,你基本上会用一个火箭筒从火箭筒中走出来,你基本上会创建很多看不见的机会来通过引用返回局部变量。
答案 3 :(得分:-1)
是的,它有来按值捕获,因为否则你可能会尝试捕获一个变量(例如通过引用),当实际调用lambda / function时它不再存在。 / p>
该标准支持按值和按引用捕获,以解决两种可能的用例。如果告诉编译器按值捕获,则会在创建lambda时捕获它。如果你要求通过引用捕获它,它将捕获对变量的引用,然后在lambda被调用的时候使用该引用(当然要求引用的变量必须仍然存在打电话。)