【问题标题】:Lambda: A by-reference capture that could dangleLambda:一个可以悬空的引用捕获
【发布时间】:2015-10-24 12:51:06
【问题描述】:

Scott Meyers,在 Effective Modern C++ 中,在 lambda 章节中说:

考虑以下代码:

void addDivisorFilter()
{
    auto calc1 = computeSomeValue1();
    auto calc2 = computeSomeValue2();

    auto divisor = computeDivisor(calc1, calc2);

    filters.emplace_back(
          [&](int value) { return value % divisor == 0; }
    );
}

此代码是一个等待发生的问题。 lambda 引用局部变量divisor,但当addDivisorFilter 返回时,该变量不再存在。那是在filters.emplace_back 返回之后,所以添加到filters 的函数在到达时基本上是死的。使用该过滤器几乎从创建的那一刻起就会产生未定义的行为。

问题是:为什么它是未定义的行为?据我了解,filters.emplace_back 仅在 lambda 表达式完成后返回,并且在执行期间,divisor 有效。

更新

我错过的一个重要数据是:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;

【问题讨论】:

    标签: c++ c++11 lambda c++14


    【解决方案1】:

    那是因为向量filters 的作用域比函数的作用域长。在函数退出时,向量filters 仍然存在,并且捕获的对divisor 的引用现在悬空。

    据我了解,filters.emplace_back 仅在 lambda 表达式完成后返回,并且在执行期间除数有效。

    那不是真的。向量存储从闭包创建的 lambda,并且不会“执行”lambda,您在函数退出后执行 lambda。从技术上讲,lambda 是由一个闭包(一个依赖于编译器的命名类)构造的,该闭包在内部使用引用,例如

    #include <vector>
    #include <functional>
    
    struct _AnonymousClosure
    {
        int& _divisor; // this is what the lambda captures
        bool operator()(int value) { return value % _divisor == 0; }
    };
    
    int main()
    {
        std::vector<std::function<bool(int)>> filters;
        // local scope
        {
            int divisor = 42;
            filters.emplace_back(_AnonymousClosure{divisor});
        }
        // UB here when using filters, as the reference to divisor dangle
    }
    

    【讨论】:

    • 使用 OP 函数示例,如果我通过 using FilterContainer = std::vector&lt;bool&gt;; 定义过滤器,该引用可能会悬空?
    • @Amadeus 如果他存储bool,他就不必首先处理悬空引用,因为所有std::vector 将包含true/false 值.但是他存储了std::function,而不是返回bool,并且返回值取决于除数,他需要它来计算bool的返回值。
    • @Amadeus 确保存储在向量中的任何内容都不会存储过期引用。
    【解决方案2】:

    当 addDivisorFilter 处于活动状态时,您没有评估 lambda 函数。您只是将“函数”添加到集合中,不知道何时评估它(可能在 addDivisorFilter 返回很久之后)。

    【讨论】:

      【解决方案3】:

      除了@vsoftco的回答,下面修改的示例代码让你体验一下问题:

      #include <iostream>
      #include <functional>
      #include <vector>
      
      void addDivisorFilter(std::vector<std::function<int(int)>>& filters)
      {
          int divisor = 5;
      
          filters.emplace_back(
                [&](int value) { return value % divisor == 0; }
          );
      }
      
      int main()
      {
          std::vector<std::function<int(int)>> filters;
          addDivisorFilter(filters);
          std::cout << std::boolalpha << filters[0](10) << std::endl;
          return 0;
      }
      

      live example

      此示例在运行时生成 Floating point exception,因为在 main 中计算 lambda 时,对 divisor 的引用无效。

      【讨论】:

      • 哦,是真的。我错过了他说的那部分using FilterContainer = std::vector&lt;std::function&lt;int(int)&gt;&gt;。我在想它是:using FilterContainer = std::vector&lt;bool&gt;。谢谢,你的回答很清楚
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-13
      • 1970-01-01
      • 2021-09-24
      • 1970-01-01
      • 1970-01-01
      • 2016-02-08
      • 1970-01-01
      相关资源
      最近更新 更多