I have recently been messing around with event systems in C++ like boost::signals2 and similar libraries. They all seem to have one limitation in common: they don't really work with move semantics.
If you connect your signal to a slot that is a member of an object it refers to the current memory address of that object to call the slot. Once that address changes, for example because the std::vector the object is in has to reallocate it's memory array, the connection essentially breaks. It still refers to the moved-from address.
I am a little bit confused on how to correctly use such signal/slot libraries. Do you really just have to make sure the slot doesn't never changes it's location, for example by placing it on the heap? Or is there a way to make the signal automatically be aware of the changed slot location?
The thing is that slot's location in memory doesn't depend on instances data. Member function exists in memory as single definition for all instances of the class and doesn't change its location. When you call the function for specific object (like object->member_func()
), "this" pointer is implicitly passed among with other args, so the function knows which object it is called for. https://www.tutorialspoint.com/cplusplus/cpp_this_pointer.htm
Here is a simple example of boost signals2 signal usage:
class Handler
{
private:
std::vector<std::string> array;
public:
void member_func(int value) {}
};
#include <boost/signals2.hpp>
void test()
{
boost::signals2::signal<void(int)> signal;
Handler object;
signal.connect(std::bind( &Handler::member_func, &object, std::placeholders::_1 ));
signal(5);
}
We pass pointer to the object (&object
) in bind method args, so when the signal will be invoked, boost will call object.member_func();
And if you add any data members to Handler class or change them in runtime, that doesn't affect the connections.
The only thing here you need to care is the lifetime of the object: you must disconnect slot before deleting the object. Otherwise, when signal is invoked, object.member_func();
call leads to undefined behavior because the object doesn't exist any more.