了解 C++ lambda 函数中的引用捕获

Understanding capture by reference in C++ lamdba functions

在我遇到这种情况之前,我以为我了解 C++ 中引用捕获的工作原理:

auto inrcrementer = []() {
    int counter = 0;
    return [&counter]() {
        return counter++;
    };
};

int main() {
    auto inc = inrcrementer();
    cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;

    return 0;
}

我原以为 return 0, 1, 2 但结果 return 0, 32765, 32765。为什么?

此外,如果我将其更改为:

auto inrcrementer = []() {
    int counter = 0;
    return [counter]() mutable {
        return counter++;
    };
};

int main() {
    auto inc = inrcrementer();
    cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;

    return 0;
}

已修复,并且 return 符合预期。两者有什么区别?

问题在于,在您的第一个示例中,lambda 存储了对 counter 的引用,而不是其副本。但是当 incrementer returns 时 counter 超出范围,因此引用悬而未决。然后,当您调用 incrementer 返回的 lambda 时,该引用无效。

这在实践中通常意味着它指向堆栈中已经/正在用于其他用途的区域。这就是为什么你会得到奇怪的结果。

在您的第二个示例中,lambda 有自己的 counter 副本,并且只要 lambda 仍然存在,它就一直在范围内。因为你已经声明了 lambda mutable,它可以用它的 counter 的(私有)副本做任何它喜欢的事情,而不会被其他人弄乱。

因此通过引用捕获可能很危险。小心使用!引用实际上只是一个指针,在表面之下,但因为它看起来不像一个指针,所以很容易滑倒。

展开外层lambda后,你的第一个例子可以这样写:

struct incrementer
{
   auto operator()()
   {
     int counter = 0;
     return [&counter]() { return counter++;}
   }
};

随着它现在变得更加明显,counter 是一个局部变量,内部 lambda 操作对局部对象的引用,一个在调用站点的悬空对象。

第二个例子可以展开成这样:

struct incrementer
{
   auto operator()()
   {
     int counter = 0;
     struct inner
     {
       inner(int cnt) : mcnt{cnt}();
       int operator()() { return mcnt++; }
       int mcnt;
     };
     return inner{counter};
   }
};

基本上,计数器的生命周期需要在 lambda 的整个生命周期内以某种方式保留。

另一种方法是将计数器存储在外层 lambda 中,例如

auto inrcrementer = [counter=0]() mutable {
    return [&counter]()  {
        return counter++;
    };
};

只要内层的寿命不超过外层就可以了。