使用 std::function 作为成员函数,它捕获 `this`,并在析构函数之后从复制的 lambda 中访问它

Use std::function as member function, which capture `this`, and access it from the copied lambda after destructor

Flex Ferrum post a code sample here(我认为Minimal, Complete, and Verifiable就够了):

#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

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

啊,应该怪临时的析构函数- Bang(100500),returns形式GetBang,是prvalue, and has temporary object lifetime.

  1. [this] will be stored as reference of *this,像这样:

    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输出一些信息:


#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的地址不变。

这两个保证我们仍然可以从 bm_foo() 访问临时的 m_value

应该是Undefined Behaviour来访问一个已经被销毁的对象,但是不需要警告和错误。

更新

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

  1. 就像 指出的那样,使用初始化程序捕获:[bang = *this]。这需要 c++14.
  2. 通过复制捕获当前对象的更简单方法:[*this]。这需要 c++17。 live demo

您可能希望通过值 *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