我正在尝试用c ++设计信号和插槽系统。该机制受到boost :: signal的启发,但应该更简单。我正在使用MSVC 2010,这意味着有一些c ++ 11功能可用,但遗憾的是可变参数模板不是。
首先,让我提供一些背景信息。我实现了一个处理数据的系统,该系统由连接到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)通常信号和槽将使用const引用复杂数据类型作为参数。使用boost :: signal,需要使用boost :: cref来提供引用。我想避免这种情况。如果我按如下方式创建一个Signal实例和一个Slot实例,是否保证参数作为const引用传递?
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将任何函数指针或方法指针连接到信号,这可能很有用。
答案 0 :(得分:0)
这是我的方法:
重量比助推器轻得多,但不能处理汇总响应。
我认为将shared_ptr用作回调的所有者,并使用weak_ptr用作信号引发器,这可以确保回调仍在使用中。
我还喜欢它如何自动清除已死的weak_ptr回调。
template <typename... FuncArgs>
class Signal
{
using fp = std::function<void(FuncArgs...)>;
std::forward_list<std::weak_ptr<fp> > registeredListeners;
public:
using Listener = std::shared_ptr<fp>;
Listener add(const std::function<void(FuncArgs...)> &cb) {
// passing by address, until copy is made in the Listener as owner.
Listener result(std::make_shared<fp>(cb));
registeredListeners.push_front(result);
return result;
}
void raise(FuncArgs... args) {
registeredListeners.remove_if([&args...](std::weak_ptr<fp> e) -> bool {
if (auto f = e.lock()) {
(*f)(args...);
return false;
}
return true;
});
}
};
用法:
Signal<int> bloopChanged;
// ...
Signal<int>::Listener bloopResponse = bloopChanged.add([](int i) { ... });