我想在C++中设计一个信号和插槽系统。该机制有点受到boost :: signal的启发,但应该更简单。我正在使用MSVC 2010,这意味着一些C++ 11功能可用,但可悲的variadic模板不可用。Slim C++信号/事件机制与插槽的移动语义
首先,让我给出一些上下文信息。我实现了一个系统来处理由连接到PC的不同硬件传感器产生的数据。每个硬件传感器都由一个继承自通用类设备的类表示。每个传感器作为接收数据的单独线程来运行,并且可以将其转发给几个类(例如过滤器,可视化器等)。换句话说,Device是一个信号,Processor是一个插槽或监听器。整个信号/插槽系统应该非常高效,因为传感器会生成大量数据。
下面的代码显示了我的第一种方法的信号与一个参数。可以添加(复制)更多模板特化,以包括对更多参数的支持。以下代码中缺少线程安全性(需要使用互斥锁来同步对slots_vec的访问)。
我想确保插槽(即处理器实例)的每个实例都不能被另一个线程使用。因此,我决定使用unique_ptr和std :: move来实现插槽的移动语义。这应该确保当且仅当插槽被断开或信号被破坏时插槽也被破坏。
我想知道这是否是一种“优雅”的方法。任何使用下面的Signal类的类现在都可以创建一个Signal的实例或从Signal继承来提供典型的方法(即连接,发射等)。
#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;
};
关于该方法另外的问题:
1)一般信号和槽将使用复杂的数据类型作为参数常量的引用。使用boost :: signal需要使用boost :: cref来提供引用。我想避免这种情况。如果我按照如下方式创建一个Signal实例和一个Slot实例,是否保证参数是作为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不需要插槽类型(接收器不必从通用插槽类型继承)。实际上可以连接任何函子或函数指针。这实际上是如何工作的?如果使用boost :: function将任何函数指针或方法指针连接到信号,这可能会很有用。
你看过[sigslot](http://sigslot.sourceforge.net/)吗? – paddy
对于插槽的生命周期管理,boost :: signals2提供了一个方法slot :: track。见[这里](http://stackoverflow.com/questions/14882867/boostsignals2-descruction-of-an-object-with-the-slot)。 – spinxz