Slim c ++信号/事件机制,具有插槽的移动语义

时间:2013-01-22 13:24:43

标签: c++ templates boost signals-slots

我正在尝试用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将任何函数指针或方法指针连接到信号,这可能很有用。

1 个答案:

答案 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) { ... });