О чем эта статья
Сегодня я расскажу про библиотеку 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<int(), 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"
Заключение
Сигналы и слоты очень удобны в том случае, когда нужно уменьшить связность различных объектов. Раньше, чтобы вызывать одни объекты из других, я передавал указатели на одни объекты в другие объекты, и это вызывало циклические ссылки и превращало мой код в кашу. Теперь я использую сигналы, которые позволяют протянуть тонкие «мостики» между независимыми объектами, и это здорово уменьшило связность моего кода. Используйте сигналы и слоты на здоровье!
Список использованной литературы:
ссылка на оригинал статьи http://habrahabr.ru/post/171471/
Добавить комментарий