I would like to create a wrapper class for boost::signals2 for modules (threads) that emits signals to slots. I.e. a module should gain typical simple signalling capabilities (e.g. a public connect(...) method) by inheriting from my Signal class. I would also like to hide the actual signal-slot implementation that is used.
A concrete slot inherits from a generic Slot base class which has a template parameter defining its signature. A slot is just a functor with the suitable signature.
This question is somewhat related to this question. Slots are stored as shared_ptr and lifetime management is required. I.e. the Signal class should hold a reference to the slot to keep it alive as long as the signal itself exits. Hence I cannot connect std::functions or similar. I have to connect shared_ptrs of the slot base class.
My current approach, without thread safety so far (MSVC 2010):
template<class FunSig>
class Slot;
template<class R>
class Slot<R()>
{
public:
typedef R Ret_type;
public:
virtual ~Slot() {}
virtual Ret_type operator()() = 0;
};
template<class R, class A1>
class Slot<R(A1)>
{
public:
typedef R Ret_type;
typedef A1 Arg1_type;
public:
virtual ~Slot() {}
virtual Ret_type operator()(Arg1_type) = 0;
};
// and so forth for more arguments
/*
Signalling class.
This class is basically a wrapper for the boost::signals2 class with
lifetime management for slots.
Slots are connected by a shared_ptr which gets stored
in a std::vector to ensure that a slot exists at least as long as the signal.
*/
template<class FunSig>
class Signal
{
public:
typedef Slot<FunSig> Slot_type;
typedef boost::signals2::signal<FunSig> BoostSignal;
typedef typename BoostSignal::slot_type BoostSlot;
public:
virtual ~Signal() {}
void connectSlot(std::shared_ptr<Slot_type> slot_ptr);
protected:
//void emitSignal( ... );
//void disconnectAllSlots();
private:
BoostSignal sig_;
/// vector of shared_ptr to slots for lifetime management
std::vector<std::shared_ptr<Slot_type> > slotsVec_;
};
template<class FunSig>
void Signal<FunSig>::connectSlot(std::shared_ptr<Slot_type> slot_ptr)
{
sig_.connect(*slot_ptr); // version A: compiler error
// OR
sig_.connect(boost::ref(*slot_ptr)); // version B: warning, but compiles and runs
// add slot pointer to vector of slots
slotsVec_.push_back(slot_ptr);
}
This code (version A) does not compile. It breaks inside boosts slot_template.hpp and at the line marked in the connectSlot method:
error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Slot<FunSig>' (or there is no acceptable conversion)
1> with
1> [
1> FunSig=void (const float &)
Interestingly this code compiles and runs if version B is used instead - i.e. a boost::ref is passed of the slot. Though there is a compiler warning "Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct." in boost's singals2 auto_buffer.hpp.
So what is the actual problem here and how to solve it? Why does this work with boost::ref and why does it not compile without it?
I am even unsure it the whole design idea is useful. The original idea was to hide the whole signalling/slot stuff in a superclass and focus on the signature (and include lifetime management).
An additional question regarding boost's signals2: the singals2 connect() method takes a reference to a slot. How is this handled internally. Does it use a reference of the connected slot or does it make a copy of the slot? This is important as my slots handle dynamically allocated memory.
I assume you mean the warning C4996. This is a feature of Microsoft's c++ standard library implementation which warns you whenever a standard algorithm is compiled with potentially unsafe arguments, e.g. in this snippet, the caller is responsible for sufficient sizes of
source
andtarget
:You should not duplicate the functionality already provided by Boost.Signals2: the library provides a sophisticated life-time management of the "slots" connected to a signal. Read through the docs, they foresee a very fine-granular control of this aspect. Also, you lose a very interesting feature - the thread safety of Boost.Signals2: You'd need to manage thread safe insertions and deletions into and from the
slotVec_
yourself which is a non-trivial undertaking...I am working on a similar abstraction library called vex myself, have a look at
abstract_multicast_delegate
andabstract_signal
invex/functional/contracts
. The implementation based onsignals2::signal
can be found invex/functional/implementation/bs2_signal.h
. This is however still a playground, I am experimenting with alternative implementations too...EDIT: Sorry, I don't know how to point to the tip of a hg repository at codeplex. I had to remove the links since a lot has changed since yesterday...
This question has been answered in a different context here.
Actually std::ref or boost::ref have to be used because boost::signals2 connect method copies its arguments. But the class Slot is not copyable as it is an abstract class. Hence using boost::ref is the recommended solution because it makes the slot copyable.