4

标准是否定义了这段代码会发生什么?

#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 对象是通过引用传递的,则不会复制任何捕获的值。并且在调用函数之前不会对捕获的值进行复制,因此在调用之间会保留对捕获值的突变。

4

3 回答 3

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这里做了什么。

于 2012-04-09T13:36:56.103 回答
4

我发现通过手动将 lambda 扩展为结构/类最容易理解这种行为,这或多或少是这样的(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);

您的功能callfunccallfuncref操作或多或少在这种类型的对象上。现在让我们检查您所做的调用:

callfunc(foo);

这里你通过值传递,所以foo将使用默认的复制构造函数进行复制。中的操作callfunc只会影响复制值的内部状态,实际 foo 对象中的状态不会改变。

callfunc(foo);

同样的东西

callfuncref(foo);

啊,这里我们通过引用传递 foo,所以callfuncref(调用operator())将更新实际foo对象,而不是临时副本。这将导致n之后foo被更新11,这是常规pass-by-reference行为。因此,当您再次调用它时:

callfunc(foo);

您将再次对一个副本进行操作,但将foowhere的副本n设置为 11。这显示了您的期望。

于 2012-04-09T13:32:31.650 回答
2

[&]除非您明确地全部捕获或通过引用捕获特定变量,否则该值是通过复制捕获的[&n]。所以整个输出是标准的。

于 2012-04-09T13:12:52.727 回答