![image](https://habrastorage.org/getpro/habr/post_images/4b2/dcc/45a/4b2dcc45aff2a2e44386286d4010f05c.png)
О чем эта статья
Сегодня я расскажу про библиотеку Boost Signals — про сигналы, слоты, соединения, и как их использовать.
Сигнал — это тип данных, который может хранить в себе несколько функций обратного вызова, и вызывать их.
Слот — это, соответственно, и есть подсоединяемые к сигналу функции.
Как уже было сказано, к одному сигналу можно подключить несколько функции, и при вызове сигнала, подключенные функции вызываются в порядке их подключения.
Похожую систему иногда называют событийной, тогда сигналы — это события, а слоты — это подписанные на определенные события обработчики.
Простой пример
Допустим, мы делаем UI для игры. У нас в игре будет много кнопок, и каждая кнопка по нажатию будет выполнять определенные действия. И хотелось бы при этом, чтобы все кнопки принадлежали одному типу Button — то есть требуется отделить кнопку от выполняемого по нажатию на нее кода. Как раз для такого разделения и нужны сигналы.
Объявляется сигнал очень просто. Объявим его как член класса:
#include "boost/signals.hpp"
class Button
{
public:
boost::signal<void()> OnPressed; //Сигнал
};
Здесь мы создаем сигнал, который не принимает параметров и не возвращает значения.
Теперь мы можем подключить к этому сигналу слот. Проще всего в качестве слота использовать функцию:
void FunctionSlot()
{
std::cout<<"FunctionSlot called"<<std::endl;
}
...
Button mainButton;
mainButton.OnPressed.connect(&FunctionSlot); //Подключаем слот
Кроме функции, можно подключить также функциональный объект:
struct FunctionObjectSlot
{
void operator()()
{
std::cout<<"FunctionObjectSlot called"<<std::endl;
}
};
...
//Подключаем функциональный объект
mainButton.OnPressed.connect(FunctionObjectSlot());
Иногда, если кода очень мало, удобнее писать анонимную функцию и сразу же ее подключать:
//Подключаем анонимную функцию
mainButton.OnPressed.connect([]() { std::cout<<"Anonymous function is called"<<std::endl; });
Если необходимо вызвать метод объекта — его тоже можно подключить, воспользовавшись синтаксисом boost::bind:
#include "boost/bind.hpp"
class MethodSlotClass
{
public:
void MethodSlot()
{
std::cout<<"MethodSlot is called"<<std::endl;
}
};
...
MethodSlotClass methodSlotObject;
//Подключаем метод
mainButton.OnPressed.connect(boost::bind(&MethodSlotClass::MethodSlot, &methodSlotObject));
Про Boost Bind я, вероятно, напишу отдельную статью.
Таким образом, мы подключили к сигналу сразу несколько слотов. Для того, чтобы «послать» сигнал, следует вызвать оператор скобки () для сигнала:
mainButton.OnPressed();
При этом слоты будут вызваны в порядке их подключения. В нашем случае вывод будет таким:
FunctionSlot called
FunctionObjectSlot called
Anonymous function is called
MethodSlot is called
Сигналы с параметрами
Сигналы могут содержать параметры. Вот пример объявления слота, который содержит параметры:
boost::signal<void(int, int)> SelectCell;
В этом случае, очевидно, и функции должны быть с параметрами:
void OnPlayerSelectCell(int x, int y)
{
std::cout<<"Player selected cell: "<<x<<", "<<y<<std::endl;
}
//Передаем функцию с параметрами:
SelectCell.connect(&OnPlayerSelectCell);
//Или так:
SelectCell.connect([](int x, int y) { std::cout<<"Player selected cell: "<<x<<", "<<y<<std::endl; });
//Вызываем сигнал с параметрами:
SelectCell(10, -10);
Сигналы, возвращающие объекты
Сигналы могут возвращать объекты. С этим связана одна тонкость — если вызвано несколько слотов, то ведь, в сущности, возвращается несколько объектов, не так ли? Но сигнал, в свою очередь, может вернуть только один объект. По умолчанию сигнал возвращает объект, который был получен от последнего слота. Однако, мы можем передать в сигнал свой собственный «агрегатор», который скомпонует возвращенные объекты в одно.
Я, конечно, не встречал ситуации, когда мне требуется возвращать значения от сигнала, ну да ладно. Предположим, в нашем случае сигнал вызывает слоты, которые возвращают строки, а сигнал должен вернуть строку, склеенную из этих строк.
Пишется такой агрегатор просто — нужно создать структуру с шаблонных оператором «скобки», принимающим в качестве параметров итераторы:
struct Sum
{
template<typename InputIterator>
std::string operator()(InputIterator first, InputIterator last) const
{
//Нет слотов - возвращаем пустую строку:
if (first == last)
{
return std::string();
}
//Иначе - возвращаем сумму строк:
std::string sum;
while (first != last)
{
sum += *first;
++first;
}
return sum;
}
};
//Функции для проверки:
auto f1 = []() -> std::string
{
return "Hello ";
};
auto f2 = []() -> std::string
{
return "World!";
};
boost::signal<std::string(), Sum> signal;
signal.connect(f1);
signal.connect(f2);
std::cout<<signal()<<std::endl; //Выводит "Hello World!"
Отключение сигналов
Для того, чтобы отключить все сигналы от слота, следует вызвать метод disconnect_all_slots.
Для того, чтобы управлять отдельным слотом, придется при подключении слота создавать отдельный объект типа boost::connection.
Примеры:
//Отключаем все слоты
mainButton.OnPressed.disconnect_all_slots();
//Создаем соеднинение с слотом FunctionSlot
boost::signals::connection con = mainButton.OnPressed.connect(&FunctionSlot);
//Проверяем соединение
if (con.connected())
{
//FunctionSlot все еще подключен.
mainButton.OnPressed(); //Выводит "FunctionSlot called"
}
con.disconnect(); // Отключаем слот
mainButton.OnPressed(); //Не выводит ничего
Порядок вызова сигналов
Не знаю, когда это может пригодиться, но при подключении слотов можно указать порядок вызова:
mainButton.OnPressed.connect(1, &FunctionSlot);
mainButton.OnPressed.connect(0, FunctionObjectSlot());
mainButton.OnPressed(); //Вызовет сначала "FunctionObjectSlot called", а затем "FunctionSlot called"
Заключение
Сигналы и слоты очень удобны в том случае, когда нужно уменьшить связность различных объектов. Раньше, чтобы вызывать одни объекты из других, я передавал указатели на одни объекты в другие объекты, и это вызывало циклические ссылки и превращало мой код в кашу. Теперь я использую сигналы, которые позволяют протянуть тонкие «мостики» между независимыми объектами, и это здорово уменьшило связность моего кода. Используйте сигналы и слоты на здоровье!
Список использованной литературы:
www.boost.org/doc/libs/1_53_0/doc/html/signals.html