I am trying to design a signal and slot system in c++. The mechanism is somewhat inspired by boost::signal but should be simpler. I am working with MSVC 2010 which means that some c++11 features are available but sadly variadic templates are not.
First, let me give some contextual information. I implemented a system for processing data that is generated by different hardware sensors connected to the pc. Every single hardware sensor is represented by a class that inherits from a generic class Device. Every sensor is run as a separate thread which receives the data and can forward it to several Processor classes (e.g. filters, visualizers, etc.). In other words, a Device is a signal and a Processor is a slot or listener. The whole signal/slot system should be very efficient as a lot of data is generated by the sensors.
The following code shows my first approach for signals with one argument. More template specializations can be added (copied) to include support for more arguments. The thread safety is missing so far in the code below (a mutex would be required to synchronize access to slots_vec).
I wanted to make sure that every instance of a slot (i.e. a processor instance) cannot be used by another thread. Hence I decided to use unique_ptr and std::move to implement move semantics for slots. This should make sure that if and only if the slots are disconnected or when the signal is destructed the slots get destructed as well.
I am wondering if this is an "elegant" approach. Any class using the Signal class below can now either create an instance of Signal or inherit from Signal to provide the typical methods (i.e. connect, emit, etc.).
#include <memory>
#include <utility>
#include <vector>
template<typename FunType>
struct FunParams;
template<typename R, typename A1>
struct FunParams<R(A1)>
{
typedef R Ret_type;
typedef A1 Arg1_type;
};
template<typename R, typename A1, typename A2>
struct FunParams<R(A1, A2)>
{
typedef R Ret_type;
typedef A1 Arg1_type;
typedef A2 Arg2_type;
};
/**
Signal class for 1 argument.
@tparam FunSig Signature of the Signal
*/
template<class FunSig>
class Signal
{
public:
// ignore return type -> return type of signal is void
//typedef typenamen FunParams<FunSig>::Ret_type Ret_type;
typedef typename FunParams<FunSig>::Arg1_type Arg1_type;
typedef typename Slot<FunSig> Slot_type;
public:
// virtual destructor to allow subclassing
virtual ~Signal()
{
disconnectAllSlots();
}
// move semantics for slots
bool moveAndConnectSlot(std::unique_ptr<Slot_type> >& ptrSlot)
{
slotsVec_.push_back(std::move(ptrSlot));
}
void disconnectAllSlots()
{
slotsVec_.clear();
}
// emit signal
void operator()(Arg1_type arg1)
{
std::vector<std::unique_ptr<Slot_type> >::iterator iter = slotsVec_.begin();
while (iter != slotsVec_.end())
{
(*iter)->operator()(arg1);
++iter;
}
}
private:
std::vector<std::unique_ptr<Slot_type> > slotsVec_;
};
template <class FunSig>
class Slot
{
public:
typedef typename FunParams<FunSig>::Ret_type Ret_type;
typedef typename FunParams<FunSig>::Arg1_type Arg1_type;
public:
// virtual destructor to allow subclassing
virtual ~Slot() {}
virtual Ret_type operator()(Arg1_type) = 0;
};
Further questions regarding this approach:
1) Usually the signal and slots will use const references to complex data types as arguments. With boost::signal it is required to use boost::cref to feed references. I would like to avoid that. If I create a Signal instance and a Slot instance as follows, is it guaranteed that the arguments are passed as const refs?
class Sens1: public Signal<void(const float&)>
{
//...
};
class SpecSlot: public Slot<Sens1::Slot_type>
{
void operator()(const float& f){/* ... */}
};
Sens1 sens1;
sens1.moveAndConnectSlot(std::unique_ptr<SpecSlot>(new SpecSlot));
float i;
sens1(i);
2) boost::signal2 does not require a slot type (a receiver does not have to inherit from a generic slot type). One can actually connect any functor or function pointer. How does this actually work? This might be useful if boost::function is used to connect any function pointer or method pointer to a signal.