【问题标题】:When are capture variables captured?什么时候捕获变量?
【发布时间】:2018-03-08 20:23:58
【问题描述】:

我正在制作一个回调std::functions 的std::vector,但我在理解捕获时遇到了一些麻烦。如果我通过引用捕获,当我尝试使用它们时,它们似乎超出了范围。如果我按价值捕获,那么一切正常。

使用这些回调函数的代码需要特定的签名,因此假设我无法修改使用这些回调函数的代码,我需要坚持使用捕获变量而不是将事物作为函数参数传递。

什么时候localVar 被捕获?是在定义 lambda 时,还是在调用它时?答案是否会根据我是按值捕获还是按引用捕获?

这是一个我想了解的小例子:

#include <iostream>
#include <functional>
#include <vector>

int main(int argc, char **argv)
{

    int n(5);

    // make a vector of lambda functions
    std::vector<std::function<const int(void)> > fs;
    for(size_t i = 0; i < n; ++i){
        int localVar = i;
        auto my_lambda = [&localVar]()->int // change &localVar to localVar and it works
        {
            return localVar+100;
        };
        fs.push_back(my_lambda);
    }

    // use the vector of lambda functions
    for(size_t i = 0; i < n; ++i){
        std::cout << fs[i]() << "\n";
    }


    return 0;
}

【问题讨论】:

  • auto my_lambda = [&amp;localVar]()-&gt;int 行中捕获了对localVar 的引用。然后你执行push_back,然后localVar 消失,如果你调用 lambda,你有未定义的行为。
  • 想象fsstd::vector&lt;int*&gt;,而在循环中你做了fs.push_back(&amp;localVar)。退出定义localVar 的块后,您是否希望存储的指针有效?
  • 您可以将 lambda 视为具有重载 operator() 的类的实例。如果你通过引用捕获它就像拥有一个类的引用成员;您必须注意,当仍在调用 lambda 的实例时,所引用的对象不会停止存在

标签: c++ c++11 lambda std-function


【解决方案1】:

reference 在您创建 lambda 时被捕获。被引用对象的 value 永远不会被捕获。当您调用 lambda 时,它将使用引用来确定所引用对象的值,只要您使用它(就像使用任何其他引用一样)。如果您在被引用的对象不再存在后使用引用,则您使用的是悬空引用,这是未定义的行为。

在这种情况下,auto my_lambda = [&amp;localVar]()-&gt;int 创建一个 lambda,其引用名为 localVar 的局部变量 localVar

std::cout &lt;&lt; fs[i]() &lt;&lt; "\n"; 调用其中一个 lambda。但是,当 lambda 执行 return localVar+100; 时,它会尝试使用对局部变量 localVar(第一个 for 循环的局部变量)的引用 localVar,但该局部变量不再存在。你有未定义的行为。

如果您删除与符号并按值 (auto my_lambda = [localVar]()-&gt;int) 获取 localVar,则您将捕获该值的副本,就像创建 lambda 时一样。既然是复制品,那么原来的localVar会发生什么并不重要。

您可以在http://en.cppreference.com/w/cpp/language/lambda#Lambda_capture阅读有关此内容的信息

【讨论】:

    【解决方案2】:

    如果我通过引用捕获,当我尝试使用它们时,它们似乎超出了范围

    没错。您创建了一个封装对局部变量的引用的 lambda。变量超出范围,使该引用悬空。这与任何其他参考资料没有什么不同。

    在定义 lambda 的地方捕获“发生”——这就是它的目的!如果稍后发生,当您调用 lambda 时(哪个时间?),您想要捕获的东西将早已不复存在,或者至少无法访问。

    捕获允许我们“保存”我们现在可以命名的东西,以备后用。但是如果你通过引用捕获,你最好确保引用的东西在你使用那个引用时仍然存在。

    不过,请注意 this 这样的怪事。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-14
      • 1970-01-01
      • 2016-12-10
      • 1970-01-01
      • 2021-10-08
      • 2011-04-04
      • 1970-01-01
      相关资源
      最近更新 更多