我正在尝试用 C++ 设计一个信号和插槽系统。该机制在某种程度上受到 boost::signal 的启发,但应该更简单。我正在使用 MSVC 2010,这意味着一些 c++11 功能可用,但遗憾的是可变参数模板不可用。
首先,让我提供一些上下文信息。我实现了一个系统来处理由连接到 pc 的不同硬件传感器生成的数据。每个硬件传感器都由一个从通用类Device继承的类表示。每个传感器都作为一个单独的线程运行,它接收数据并将其转发到多个处理器类(例如过滤器、可视化器等)。换句话说,设备是信号,处理器是插槽或侦听器。整个信号/插槽系统应该非常高效,因为传感器会生成大量数据。
下面的代码展示了我对带有一个参数的信号的第一种方法。可以添加(复制)更多模板特化以包括对更多参数的支持。到目前为止,下面的代码中缺少线程安全性(需要互斥锁来同步对 slot_vec 的访问)。
我想确保插槽的每个实例(即处理器实例)不能被另一个线程使用。因此我决定使用 unique_ptr 和 std::move 来实现插槽的移动语义。这应该确保当且仅当插槽断开连接或信号被破坏时,插槽也会被破坏。
我想知道这是否是一种“优雅”的方法。任何使用下面 Signal 类的类现在都可以创建 Signal 的实例或从 Signal 继承以提供典型的方法(即 connect、emit 等)。
#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) 通常信号和槽将使用对复杂数据类型的 const 引用作为参数。使用 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 用于将任何函数指针或方法指针连接到信号,这可能很有用。