簡體   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