我想在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