使用std :: function作为成员函数,捕获`this`,并在析构函数

时间:2018-06-09 09:56:46

标签: c++ lambda temporary-objects

Flex Ferrum发布代码示例here(我认为最小,完整且可验证已足够):

#include <iostream>
#include <functional>
using namespace std;

class Bang
{
public:
    Bang(int i = 0) : m_val(i)
    {
        m_foo = [this] {std::cout << m_val << std::endl;};
    }

    ~Bang()
    {
        m_val = -1;
    }

    void Foo()
    {
        m_foo();
    }
private:
    int m_val;
    std::function<void ()> m_foo;
};

Bang GetBang()
{
    return Bang(100500);
}

int main() {
    Bang b(100500);
    b.Foo();
    b = GetBang();
    b.Foo();
    return 0;
}

我们不错的Flex还提供live demo

粗略地看,我认为它会输出100500,但实际输出是:

-1
  • 为什么?背后是什么?
  • 如何解决?(输出100500,而不是-1

我在ask box中写了一些我自己的推理,但发现它更适合作为答案发布(会让问题太长)。如果我的回答有误,请更正,欢迎提供更多答案

2 个答案:

答案 0 :(得分:3)

啊,它应该归咎于临时的析构函数 - Bang(100500),它返回GetBang格式,prvalue,并且temporary object lifetime

  1. [this] will be stored as reference of *this,就像这样:
  2.     class Lambda
        {
        public:
            void operator()() const
            {
                //output
            }
    
        private:
            Bang& bang;
    
        public:
            Lambda(Bang& bang) : bang{bang}
            {
            }
    
        } lambda{*this};
        ...
        m_foo = lambda;
    
    1. 因为此处 RVO,所以,临时Bang(100500)将首先分配给b,然后销毁。

    2. 促销operator()constructordestructor输出一些信息:

    3. #include <iostream>
      #include <functional>
      
      using namespace std;
      
      class Bang
      {
      public:
          Bang(int i = 0) : m_val(i)
          {
      
              std::cout << "Bang(int i = 0) m_val address is " << &m_val << '\n';
              class Lambda
              {
              public:
                  void operator()() const
                  {
      
                      std::cout << "operator() m_val address is " << &bang.m_val << '\n';
                      std::cout << bang.m_val << std::endl;
                  }
      
              private:
                  Bang &bang;
      
              public:
                  Lambda(Bang &bang) : bang{bang}
                  {
                  }
      
              } lambda{*this};
              m_foo = lambda;
      
          }
      
          ~Bang()
          {
              std::cout << "~Bang()\n";
              m_val = -1;
          }
      
          void Foo()
          {
              m_foo();
          }
      
      private:
          int m_val;
          std::function<void()> m_foo;
      };
      
      Bang GetBang()
      {
          return Bang(100500);
      }
      
      int main()
      {
          Bang b;
          b = GetBang();
          b.Foo();
          return 0;
      }
      

      live demo

      输出:

      Bang(int i = 0) m_val address is 0x7ffd202c48b0
      Bang(int i = 0) m_val address is 0x7ffd202c48e0
      ~Bang()
      operator() m_val address is 0x7ffd202c48e0
      -1
      ~Bang()
      

      显示:

      • dtor将在输出之前被调用,这意味着临时对象已被销毁。
      • m_value的地址不会改变。

      这两个保证我们仍然可以从m_value的{​​{1}}访问临时b

      应该未定义行为来访问已销毁的对象,但不需要警告和错误。

      更新

      要解决这个问题,有两种解决方案:

      1. @Killzone Kid相似,请使用初始值设定项进行捕获:m_foo()。这需要c ++ 14。
      2. 更简单的方法来捕获当前对象的副本:[bang = *this]。这需要c ++ 17。 live demo

答案 1 :(得分:0)

您可能希望按值*this将当前对象传递给lambda,以便在复制赋值Bang时可以存储和复制它。传递指针this将存储并复制指向复制赋值Bang时已销毁的临时对象的指针。

这样可以:

#include <iostream>
#include <functional>

class Bang
{
public:
    Bang(int i = 0) : m_val(i)
    {
        m_foo = [bang = *this] { std::cout << bang.m_val << std::endl; };
    }

    ~Bang()
    {
        m_val = -1;
    }

    void Foo()
    {
        m_foo();
    }
private:
    int m_val;
    std::function<void()> m_foo;
};

Bang GetBang()
{
    return Bang(100500);
}

int main()
{
    Bang b;
    b = GetBang();
    b.Foo();
    return 0;
}

演示:https://ideone.com/LUDrBb