Search code examples
c++boostboost-signals2

Boost Signals2 pass Slot to member Function for Disconnecting


I have the following class which uses a simple boost::Signals2::Signal:

class Button {
    using OnClick = signal<void()>;

   public:
    using OnClickSlotType = OnClick::slot_type;
    boost::signals2::connection add_handler(const OnClickSlotType& slot) {
        return click.connect(slot);
    }

    void remove_handler(const OnClickSlotType& slot) {
        std::cout << "Disconnect\n";
        click.disconnect(&slot);
    }

    signal<void()> click;
};

I use the class like the following:

void demo() { std::cout << "Demo called\n"; }

void second() { std::cout << "Second\n"; }

int main() {
    Button btn;

    btn.add_handler(&demo);
    btn.add_handler(&second);

    btn.click();

    btn.remove_handler(&demo);

    btn.click();
}

But the Function demo is not disconnected. The output is always:

Demo called
Second
Disconnect
Demo called
Second

How can i correctly disconnect the Function from the Signal?


Solution

  • You could register the same function more than once, resulting in multiple connections.

    Therefore, the function is not sufficient identity.

    Instead, you can use the connection object to disconnect a particular connection:

    Live On Coliru

    auto d = btn.add_handler(&demo);
    btn.add_handler(&second);
    
    btn.click();
    
    d.disconnect();
    
    btn.click();
    

    Prints

    Demo called
    Second
    Disconnect doesn't require access to either source or subscriber
    Second
    

    The beauty of this is that it decouples sources, subscribers and connections. You could have a table of connections and disconnect them without ever needing to know the parties involved.

    BONUS: scoped_connection

    Scoped connections are a RAII wrapper for connections. This means that you can have connections disconnect in an exception safe manner tied to the life-time of a wrapper. This is awesome for protecting against lifetime issues:

    Live On Coliru

    #include <boost/signals2/signal.hpp>
    #include <iostream>
    #include <optional>
    using boost::signals2::signal;
    
    class Button {
        using OnClick = signal<void(std::string const&)>;
    
       public:
        using OnClickSlotType = OnClick::slot_type;
        boost::signals2::connection add_handler(const OnClickSlotType& slot) {
            return click.connect(slot);
        }
    
        OnClick click;
    };
    
    struct Demo {
        Demo(Button& btn, std::string name)
            : _connection(btn.add_handler(std::ref(*this))),
              _name(std::move(name))
        { }
    
        Demo(Demo const&) = delete;
        Demo(Demo&&) = delete;
    
        void operator()(std::string const& msg) const {
            std::cout << _name << " called (" << msg << ")\n";
        }
      private:
        boost::signals2::scoped_connection _connection;
        std::string _name;
    };
    
    int main() {
        Button btn;
    
        std::optional<Demo> foo;
        {
            Demo bar(btn, "bar");
    
            btn.click("first click L:" + std::to_string(__LINE__));
    
            foo.emplace(btn, "foo");
    
            btn.click("second click L:" + std::to_string(__LINE__));
    
            foo.reset();
    
            btn.click("third click L:" + std::to_string(__LINE__));
        } // bar is disconnecteded
    
        // no connections left
        btn.click("last click L:" + std::to_string(__LINE__));
    }
    

    Prints

    bar called (first click L:42)
    bar called (second click L:46)
    foo called (second click L:46)
    bar called (third click L:50)