繁体   English   中英

在类成员中使用std :: bind和std :: function会导致回调使用旧对象引用?

[英]Using std::bind and std::function with a class member causes the callback to use an old object reference?

这令人困惑,但是基本上我所拥有的是一个使用c ++ 11 std::functionstd::bind具有回调函数的类。 一切正常。 但是,当我需要通过重新分配对象来重置所有内容时,似乎新对象并未使所有引用正确。 这是一个演示我的问题的示例程序:

#include <functional>
#include <list>
#include <iostream>
#include <string>

class OtherClass
{
public:
    OtherClass() = default;

    void RegisterCallback(std::function<void(void)> f) {
        callback = f;
    }

    void PrintThings() {
        callback();
    }

    std::function<void(void)> callback;
};

class MyClass
{
public:
    MyClass() {
        list_of_things.push_back("thing1");
        list_of_things.push_back("thing2");
        list_of_things.push_back("thing3");
        list_of_things.push_back("thing4");

        other_class.RegisterCallback(std::bind(&MyClass::MyFunction, this));
    }

    void PrintThings() {
        MyFunction();
        other_class.PrintThings();
    }

    void MyFunction() {
        auto a = this;
        for (auto& thing: list_of_things)
        {
            std::cout << thing << std::endl;
        }
    }

    OtherClass other_class;
    std::list<std::string> list_of_things;
};

int main()
{
    MyClass my_class;
    my_class.PrintThings();
    my_class = MyClass();
    my_class.PrintThings();
    std::cout << "done" << std::endl;
}

这有点令人困惑,但是基本上,如果您使用调试标志进行编译并逐步执行调试,您会发现我们第一次调用my_class.PrintThings()它将打印两次。 一次用于MyFunction()调用,一次用于other_class.PrintThings()调用,该调用将MyFunction作为回调。 然后,我们用my_class = MyClass()替换对象,并调用新的构造函数。 当我们逐步执行时,我们发现它在对MyFunction的调用上打印列表, 而不在对other_class.PrintThings()的调用上打印列表。 MyFunction有一个变量a ,我用它来查看对象的地址。 第二次通过a具有不同的地址,具体取决于是否将MyFunction作为OtherClass的回调进行OtherClass

在此示例中,令人讨厌的鬼对象只有一个空列表(大概是由于被破坏了),但是在我遇到此问题的实际程序中,它充满了垃圾内存并导致了分段错误。 我还注意到调试器有些奇怪的行为。 当它到达幽灵对象时,它不会只是进入或结束,除非我在其中放置一个断点,否则它将跳过该函数。

到底是怎么回事? 为什么第二次回调没有正确绑定? 在析构函数中我需要做些特别的事情吗? 我是否缺少对函数指针或std::bind一些基本了解?

到底是怎么回事?

未定义的行为

为什么第二次回调没有正确绑定?

因为您创建了一个新的OtherClass作为将新的MyClass分配给my_class的一部分。 您尚未初始化其回调。

在析构函数中我需要做些特别的事情吗?

在析构函数中,分配和复制构造函数。 这是因为您要在自己中存储“我自己”的地址。 一切都很好,直到对象更改地址为止(复制时将更改地址)。 请注意,在下面的代码中,这三个都通过使用smart_ptr来处理。

一种解决方案是重构MyClass以使用pimpl习惯用法。 即,该类是地址永远不变的实现的包装。

#include <functional>
#include <iostream>
#include <list>
#include <string>
#include <memory>

class OtherClass
{
public:
    OtherClass() = default;

    void RegisterCallback(std::function<void(void)> f) {
        callback = f;
    }

    void PrintThings() {
        callback();
    }

    std::function<void(void)> callback;
};


class MyClass
{
    struct Impl
    {
        Impl()
        {
            list_of_things.push_back("thing1");
            list_of_things.push_back("thing2");
            list_of_things.push_back("thing3");
            list_of_things.push_back("thing4");
        }

        void MyFunction() 
        {
            for (auto& thing: list_of_things)
            {
                std::cout << thing << std::endl;
            }
        }

        void PrintThings() {
            MyFunction();
            other_class.PrintThings();
        }

        OtherClass other_class;
        std::list<std::string> list_of_things;
    };

    std::unique_ptr<Impl> impl_;

public:
    MyClass() 
    : impl_(std::make_unique<Impl>())
    {
        impl_->other_class.RegisterCallback(std::bind(&Impl::MyFunction, impl_.get()));
    }

    void PrintThings() {
        impl_->PrintThings();
    }


};

int main()
{
    MyClass my_class;
    my_class.PrintThings();
    my_class = MyClass();
    my_class.PrintThings();
    std::cout << "done" << std::endl;
}

预期输出:

thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
thing1
thing2
thing3
thing4
done

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM