Boost Signals2 pass Slot to member Function for Disconnecting

381 views Asked by At

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?

1

There are 1 answers

0
sehe On BEST ANSWER

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)