Boost Signals — сигналы и слоты для C++

от автора

image

О чем эта статья

Сегодня я расскажу про библиотеку 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" 

Заключение

Сигналы и слоты очень удобны в том случае, когда нужно уменьшить связность различных объектов. Раньше, чтобы вызывать одни объекты из других, я передавал указатели на одни объекты в другие объекты, и это вызывало циклические ссылки и превращало мой код в кашу. Теперь я использую сигналы, которые позволяют протянуть тонкие «мостики» между независимыми объектами, и это здорово уменьшило связность моего кода. Используйте сигналы и слоты на здоровье!

Список использованной литературы:

www.boost.org/doc/libs/1_51_0/doc/html/signals.html

ссылка на оригинал статьи http://habrahabr.ru/post/171471/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *