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

в 22:01, , рубрики: boost, c++, c++11, signals, С++, метки: , , ,

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

Автор: Mephi1984

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js