我发现使用C ++ 0x闭包令人困惑。我的初始report和subsequent one产生了比解释更多的混乱。下面我将向您展示麻烦的示例,我希望找出代码中存在未定义行为的原因。代码的所有部分都没有任何警告地通过gcc 4.6.0编译器。
#include <iostream>
int main(){
auto accumulator = [](int x) {
return [=](int y) -> int {
return x+y;
};
};
auto ac=accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
产出符合预期:
2 2 2
2 2 2
2 2 2
#include <iostream>
int main(){
auto accumulator = [](int x) {
return [&](int y) -> int {
return x+=y;
};
};
auto ac=accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
输出结果为:
4 3 2
7 6 5
10 9 8
#include <iostream>
#include <functional> // std::function
int main(){
typedef std::function<int(int)> fint2int_type;
typedef std::function<fint2int_type(int)> parent_lambda_type;
parent_lambda_type accumulator = [](int x) -> fint2int_type{
return [=](int y) -> int {
return x+y;
};
};
fint2int_type ac=accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
输出结果为:
2 2 2
2 2 2
2 2 2
#include <iostream>
#include <functional> // std::function
int main(){
typedef std::function<int(int)> fint2int_type;
typedef std::function<fint2int_type(int)> parent_lambda_type;
parent_lambda_type accumulator = [](int x) -> fint2int_type{
return [&](int y) -> int {
return x+=y;
};
};
fint2int_type ac=accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
该计划的第一次运行给出:
4 3 2
4 3 2
12364812 12364811 12364810
同一计划的第二轮:
4 3 2
4 3 2
1666060 1666059 1666058
第三个:
4 3 2
4 3 2
2182156 2182155 2182154
我如何使用std :: function打破代码?为什么程序1 - 3运行良好,而程序4正确调用ac(1)三次(!)?为什么4号程序卡在接下来的三个案例中,好像变量x已被值捕获,而不是参考。并且ac(1)的最后三个调用完全不可预测,好像对x的任何引用都将丢失。
答案 0 :(得分:9)
我希望找出原因 代码中未定义的行为
每当我处理复杂而错综复杂的lambda时,我觉得首先将其转换为函数 - 对象形式会更容易。因为lambdas只是函数对象的语法糖,并且对于每个lambda,都存在与对应的函数对象的一对一映射。本文解释了如何进行翻译: http://blogs.msdn.com/b/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx
例如,你的程序没有2:
#include <iostream>
int main(){
auto accumulator = [](int x) {
return [&](int y) -> int {
return x+=y;
};
};
auto ac=accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
将由编译器大致翻译成这一个:
#include <iostream>
struct InnerAccumulator
{
int& x;
InnerAccumulator(int& x):x(x)
{
}
int operator()(int y) const
{
return x+=y;
}
};
struct Accumulator
{
InnerAccumulator operator()(int x) const
{
return InnerAccumulator(x); // constructor
}
};
int main()
{
Accumulator accumulator;
InnerAccumulator ac = accumulator(1);
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
现在,问题变得非常明显:
InnerAccumulator operator()(int x) const
{
return InnerAccumulator(x); // constructor
}
这里InnerAccumulator的构造函数将引用x,这是一个局部变量,一旦退出operator()作用域就会死掉。所以,是的,你只是得到了一个明确的旧的未定义的行为,因为你怀疑。
答案 1 :(得分:2)
让我们尝试完全无辜的东西:
#include <iostream>
int main(){
auto accumulator = [](int x) {
return [&](int y) -> int {
return x+=y;
};
};
auto ac=accumulator(1);
//// Surely this should be a no-op?
accumulator(666);
//// There are no side effects and we throw the result away!
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
}
多田:
669 668 667
672 671 670
675 674 673
当然,这也不是保证行为。实际上,在启用优化的情况下,gcc将消除accumulator(666)
调用它的死代码,我们再次获得原始结果。完全有权这样做;在一致的程序中,删除调用确实不会影响语义。但是在未定义行为的领域,可能会发生 任何。
auto ac=accumulator(1);
std::cout << pow(2,2) << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
std::cout << ac(1) << " " << ac(1) << " " << ac(1) << " " << std::endl;
未启用优化,我得到以下内容:
4
1074790403 1074790402 1074790401
1074790406 1074790405 1074790404
1074790409 1074790408 1074790407
启用优化后,
4
4 3 2
7 6 5
10 9 8
同样,C ++没有也无法提供真正的词法闭包,其中局部变量的生命周期将超出其原始范围。这将需要将垃圾收集和基于堆的本地人带到语言中。
这完全是学术性的,因为通过复制捕获x
可以使程序定义明确并按预期工作:
auto accumulator = [](int x) {
return [x](int y) mutable -> int {
return x += y;
};
};
答案 2 :(得分:1)
好吧,当所指对象消失时,引用会变得晃来晃去。如果对象A具有对对象B的某个部分的引用,则设置非常脆弱,除非对象A在某种程度上可以保证对象B的生命周期(例如,当A将shared_ptr保存到B时,或者两者都在相同的范围)。
lambda中的引用并不是一个神奇的例外。如果您打算返回x+=y
的引用,则最好确保x
足够长。这里是作为调用int x
的一部分初始化的参数accumulator(1)
。当函数返回时,函数参数的生命周期结束。