Я пытаюсь разработать систему сигналов и слотов на С ++. Этот механизм в некоторой степени вдохновлен boost :: signal, но должен быть проще. Я работаю с MSVC 2010, что означает, что некоторые функции C ++ 11 доступны, но, к сожалению, вариативные шаблоны - нет.
Во-первых, позвольте мне дать некоторую контекстную информацию. Я реализовал систему для обработки данных, которые генерируются различными аппаратными датчиками, подключенными к компьютеру. Каждый аппаратный датчик представлен классом, который наследуется от универсального класса Device. Каждый датчик запускается как отдельный поток, который получает данные и может пересылать их нескольким классам процессора (например, фильтрам, визуализаторам и т. Д.). Другими словами, Устройство - это сигнал, а Процессор - это слот или слушатель. Вся система сигнал / слот должна быть очень эффективной, так как датчики генерируют много данных.
В следующем коде показан мой первый подход к сигналам с одним аргументом. Можно добавить (скопировать) дополнительные специализации шаблонов, чтобы включить поддержку большего количества аргументов. Безопасность потоков пока отсутствует в приведенном ниже коде (для синхронизации доступа к 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 используется для подключения любого указателя функции или указателя метода к сигналу.