标准是否定义了此代码会发生什么?
#include <iostream>
template <typename Func>
void callfunc(Func f)
{
::std::cout << "In callfunc.\n";
f();
}
template <typename Func>
void callfuncref(Func &f)
{
::std::cout << "In callfuncref.\n";
f();
}
int main()
{
int n = 10;
// n is captured by value, and the lambda expression is mutable so
// modifications to n are allowed inside the lambda block.
auto foo = [n]() mutable -> void {
::std::cout << "Before increment n == " << n << '\n';
++n;
::std::cout << "After increment n == " << n << '\n';
};
callfunc(foo);
callfunc(foo);
callfuncref(foo);
callfunc(foo);
return 0;
}
g ++的输出是:
$ ./a.out
In callfunc.
Before increment n == 10
After increment n == 11
In callfunc.
Before increment n == 10
After increment n == 11
In callfuncref.
Before increment n == 10
After increment n == 11
In callfunc.
Before increment n == 11
After increment n == 12
标准是否需要此输出的所有功能?
特别是,如果创建了lambda对象的副本,则还会复制所有捕获的值。但是,如果通过引用传递lambda对象,则不会复制捕获的值。并且在调用函数之前没有捕获值的副本,因此在调用之间保留捕获值的突变。
答案 0 :(得分:9)
lambda的类型只是一个类(n3290§5.1.2/ 3),其中operator()
执行正文(/ 5),隐式复制构造函数(/ 19)和捕获复制副本等同于将其初始化(/ 21)到此类的非静态数据成员(/ 14),并且该变量的每次使用都由相应的数据成员(/ 17)替换。在此转换之后,lambda表达式只变为此类的一个实例,并遵循C ++的一般规则。
这意味着,您的代码应以与以下相同的方式工作:
int main()
{
int n = 10;
class __Foo__ // §5.1.2/3
{
int __n__; // §5.1.2/14
public:
void operator()() // §5.1.2/5
{
std::cout << "Before increment n == " << __n__ << '\n';
++ __n__; // §5.1.2/17
std::cout << "After increment n == " << __n__ << '\n';
}
__Foo__() = delete;
__Foo__(int n) : __n__(n) {}
//__Foo__(const __Foo__&) = default; // §5.1.2/19
}
foo {n}; // §5.1.2/21
callfunc(foo);
callfunc(foo);
callfuncref(foo);
callfunc(foo);
}
很明显callfuncref
在这里做了什么。
答案 1 :(得分:4)
通过手动将lambda扩展为struct / class,我发现最容易理解这种行为,这或多或少会像这样(因为n
被值捕获,通过引用捕获会看起来有点像不同):
class SomeTemp {
mutable int n;
public:
SomeTemp(int n) : n(n) {}
void operator()() const
{
::std::cout << "Before increment n == " << n << '\n';
++n;
::std::cout << "After increment n == " << n << '\n';
}
} foo(n);
您的函数callfunc
和callfuncref
或多或少对此类对象有效。现在让我们来看看你做的电话:
callfunc(foo);
这里传递值,因此将使用默认的复制构造函数复制foo
。 callfunc
中的操作只会影响复制值的内部状态,实际的foo对象中没有状态改变。
callfunc(foo);
相同的东西
callfuncref(foo);
啊,这里我们通过引用传递foo,因此callfuncref
(调用operator()
)将更新实际的foo
对象,而不是临时副本。这将导致n
foo
之后更新为11
,这是常规pass-by-reference
行为。因此,当你再次调用它时:
callfunc(foo);
您将再次操作副本,但foo
的副本n
设置为11.其中显示了您的期望。
答案 2 :(得分:2)
除非您明确捕获所有[&]
或通过引用[&n]
捕获特定变量,否则将通过副本捕获该值。所以整个输出是标准的。