私はC++で信号とスロットシステムを設計しようとしています。このメカニズムは、boost :: signalに多少影響を受けていますが、もっと単純なはずです。私はMSVC2010を使用しています。つまり、一部のc ++ 11機能は使用できますが、残念ながら可変個引数テンプレートは使用できません。
まず、コンテキスト情報をいくつか示します。PCに接続されたさまざまなハードウェアセンサーによって生成されたデータを処理するためのシステムを実装しました。すべての単一のハードウェアセンサーは、ジェネリッククラスDeviceから継承するクラスによって表されます。すべてのセンサーは、データを受信し、それを複数のプロセッサークラス(フィルター、ビジュアライザーなど)に転送できる個別のスレッドとして実行されます。つまり、デバイスは信号であり、プロセッサはスロットまたはリスナーです。センサーによって大量のデータが生成されるため、信号/スロットシステム全体が非常に効率的である必要があります。
次のコードは、1つの引数を持つシグナルに対する私の最初のアプローチを示しています。より多くの引数のサポートを含めるために、より多くのテンプレートの特殊化を追加(コピー)できます。以下のコードでは、これまでのところスレッドセーフが欠落しています(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 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を使用して関数ポインターまたはメソッドポインターをシグナルに接続する場合に役立つことがあります。