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?
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:
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.
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:
#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)