【问题标题】:When is 'this' captured in a lambda?什么时候在 lambda 中捕获“this”?
【发布时间】:2016-07-13 14:45:22
【问题描述】:

我在一个类中有一个函数,它定义了一个 lambda 并将它存储在一个局部静态变量中:

class A
{
public:
    void call_print()
    {
        static auto const print_func = [this] {
            print();
        };

        print_func();
    };

    virtual void print()
    {
        std::cout << "A::print()\n";
    }
};

class B : public A
{
public:
    virtual void print() override
    {
        std::cout << "B::print()\n";
    }
};

我还执行了以下测试:

int main()
{
    A a;
    B b;

    a.call_print();
    b.call_print();
}

(Live Sample)

我期望打印的是:

A::print()
B::print()

但我真正得到的是:

A::print()
A::print()

(每个也打印相同的对象地址)

我怀疑这是由于this 捕获造成的。我假设它会在调用它时捕获this 的值,但是它似乎在定义 lambda 的那一刻被捕获。

有人能解释一下 lambda 捕获的语义吗?他们什么时候真正被提供给函数?所有捕获类型都相同,还是this 是一个特例?删除 static 解决了这个问题,但是在我的生产代码中,我实际上将 lambda 存储在一个稍重的对象中,该对象代表我稍后插入信号的插槽。

【问题讨论】:

    标签: c++ c++11 lambda


    【解决方案1】:

    这与 lambda 捕获的语义无关。这就是static 的工作原理。

    static 函数范围的变量被初始化一次。在你的整个程序中只有一个这样的对象。它将在第一次调用函数时被初始化(更具体地说,第一次执行 static 语句时)。因此,用于初始化 static 变量的表达式只会被调用一次。

    因此,如果 static 函数范围的变量使用基于函数参数之一(如 this)的数据进行初始化,那么它只会从该函数的第一次调用中获取参数。

    您的代码创建了一个 lambda。它不会在每次调用函数时创建不同的 lambda。

    您似乎想要的行为不是函数局部 static 变量,而是对象成员。所以只需在类本身中放置一个std::function 对象,如果它为空则让call_print 对其进行初始化。

    【讨论】:

    • 值得补充的是,这实际上代表了一种非常危险的编程风格,因为如果a 被删除,将来对call_print 的调用将引发未定义的行为并且很可能会崩溃。
    • @void.pointer:lambda 如何在其生成后的某个时刻捕获任何东西? Lambdas 不是魔法。它们只是声明类型的一种奇特方式。
    • @void.pointer “我希望 lambda 的容器是静态的,而不是捕获的。” 这就像 struct S { int i; }; static const S s { 42 }; 并期望 s.i每次都不一样。
    • 我意识到我的要求在引用和按值捕获的上下文中没有意义,我想我出于某种原因认为this 捕获将是一个特例。但是看到调用实际上是如何在类本身之外发生的,以后不可能捕获this。感谢您举例说明。
    • @void.pointer:这就是为什么每个人都认为你的问题是理解static 的行为。因为 lambdas 不可能神奇地捕捉到你想象中的样子。
    【解决方案2】:

    第一次调用封闭函数A::call_print() 时实例化lambda。自从您第一次在 A 对象上调用它以来,就会捕获该对象的 this。如果您颠倒调用顺序,您会看到不同的结果:

    b.call_print();
    a.call_print();
    

    输出:

    B::print()
    B::print()
    

    它更多地与函数局部静态对象的初始化语义有关,而不是 lambda 捕获。

    【讨论】:

      【解决方案3】:

      静态局部变量在它们的声明第一次执行时被初始化。

      在这种情况下,您:

      1. 创建一个调用 A.print() 的 lambda,捕获 &A ;
      2. 将该 lambda 分配给 print_func
      3. 调用该 lambda(通过 print_func
      4. 再次调用该 lambda。

      在创建 lambda 时总是会捕获捕获的值 - 在这种情况下是在第一次调用 call_print 期间。

      【讨论】:

        【解决方案4】:

        我不认为捕获是这里的问题,而是static 关键字。 想想你的代码:

        class A
        {
        public:
            void call_print()
            {
                static A* ptr = this;
        
                ptr->print();
            };
        
            virtual void print()
            {
                std::cout << "A::print()\n";
            }
        };
        
        class B : public A
        {
        public:
            virtual void print() override
            {
                std::cout << "B::print()\n";
            }
        };
        

        这与没有 lambda 的情况几乎相同。

        如果您查看代码,很明显您的调用 a.call_print(); 使用指向对象 a 的指针初始化 ptr,然后进一步使用该对象。

        【讨论】:

          【解决方案5】:

          这个问题不是关于 lambda 的行为,而是关于函数中静态变量的行为。

          变量是在代码第一次流过它时创建的,使用当时可用的任何变量。

          示例:

          #include <iostream>
          
          int foo(int v)
          {
            static int value = v;
            return v;
          };
          
          int main()
          {
            std::cout << foo(10) << std::endl;
            std::cout << foo(11) << std::endl;
          }
          

          预期:

          10
          10
          

          因为它相当于:

          foo::value = 10;
          std::cout << foo::value << std::endl;
          // foo::value = 11; (does not happen)
          std::cout << foo::value << std::endl;
          

          【讨论】:

            【解决方案6】:

            确实,捕获的值是在定义 lambda 时设置的,而不是在调用它时设置的。因为您为定义 lambda 的表达式设置了一个静态变量,所以这只发生在第一次调用函数 call_print 时(通过管理静态变量的规则)。因此,此后的所有call_print 调用实际上都会得到相同的 lambda,即其this 设置为&amp;a

            【讨论】:

              猜你喜欢
              • 2016-12-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-04-25
              • 2017-05-28
              • 1970-01-01
              相关资源
              最近更新 更多