将std :: bind和std :: function与类成员一起使用会导致回调使用旧的对象引用吗?

时间:2018-08-20 12:27:30

标签: c++ c++11 callback std-function stdbind

这令人困惑,但是基本上我所知道的是一个使用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的回调。

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

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

1 个答案:

答案 0 :(得分:0)

  

这是怎么回事?

未定义的行为

  

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

因为您创建了一个新的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