![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
TutorialBoost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 32. Boost.Signals
|
Preferred syntax | Portable syntax |
---|---|
|
|
Следующий пример пишет «Привет, мир!» с помощью сигналов и слотов. Во-первых, мы создаем сигнал sig
, сигнал, который не принимает аргументов и имеет обратное значение пустоты. Далее мы подключаем объект функции hello
к сигналу с помощью метода connect
. Наконец, используйте сигнал sig
как функцию вызова слотов, которая по очереди вызывает HelloWorld::operator()
для печати «Hello, World!».
Preferred syntax | Portable syntax |
---|---|
struct HelloWorld { void operator()() const { std::cout << "Hello, World!" << std::endl; } }; // ... // Signal with no arguments and a void return value |
struct HelloWorld { void operator()() const { std::cout << "Hello, World!" << std::endl; } }; // ... // Signal with no arguments and a void return value |
Вызов одного слота из сигнала не очень интересен, поэтому мы можем сделать программу Hello, World более интересной, разделив работу по печати «Hello, World!» на два совершенно отдельных слота. Первый слот напечатает «Привет» и может выглядеть так:
struct Hello { void operator()() const { std::cout << "Hello"; } };
Второй слот распечатает «Мир!» и новую линию, чтобы завершить программу. Второй слот может выглядеть так:
struct World { void operator()() const { std::cout << ", World!" << std::endl; } };
Как и в нашем предыдущем примере, мы можем создать сигнал sig
, который не принимает аргументов и имеет значение возврата void
. На этот раз мы подключаем оба слота hello
и world
к одному и тому же сигналу, и когда мы вызовем сигнал, оба слота будут называться.
Preferred syntax | Portable syntax |
---|---|
|
|
По умолчанию слоты называются в порядке «первый-в-первом-выходе» (FIFO), поэтому выход этой программы будет таким, как ожидалось:
Hello, World!
Слоты могут иметь побочные эффекты, и это может означать, что некоторые слоты придется вызывать раньше других, даже если они не подключены в этом порядке. Начало. Библиотека сигналов позволяет размещать слоты в группах, которые каким-то образом упорядочены. Для нашей программы «Здравствуй, Мир» мы хотим, чтобы «Здравствуй» было напечатано перед «Миром!», поэтому мы помещаем «Здравствуй» в группу, которая должна быть исполнена перед группой, в которой находится «Мир!». Для этого мы можем предоставить дополнительный параметр в начале соединения . Позвоните, чтобы указать группу. Значения группы по умолчанию
int
s и упорядочены целым числом < отношением. Вот как мы строим Hello, World:
Preferred syntax | Portable syntax |
---|---|
|
|
Эта программа правильно напечатает «Hello, World!», потому что объект Hello
находится в группе 0, которая предшествует группе 1, где находится объект World
. Групповой параметр, по сути, необязателен. Мы пропустили его в первом примере Hello, World, потому что это было ненужно, когда все слоты независимы. Так что же произойдет, если мы смешаем вызовы для подключения, которые используют групповой параметр, и те, которые этого не делают? «Неназванные» слоты (то есть те, которые были подключены без указания названия группы) могут быть размещены в передней или задней части списка слотов (путем прохождения boost::signals::at_front
или boost:::signals:::at_back
в качестве последнего параметра для connect
соответственно), и по умолчанию до конца списка. Когда группа определена, конечный параметр описывает, где слот будет размещен в групповом порядке. Если мы добавим новый слот к нашему примеру:
struct GoodMorning
{
void operator()() const
{
std::cout << "... and good morning!" << std::endl;
}
};
sig.connect
(GoodMorning());
мы получим результат, которого хотели:
Hello, World! ... and good morning!
Сигналы могут распространять аргументы на каждый из слотов, которые они называют. Например, сигнал, который распространяет события движения мыши, может захотеть пройти по новым координатам мыши и нажать кнопки мыши.
В качестве примера мы создадим сигнал, который передает два аргумента float
в его слоты. Затем мы создадим несколько слотов, которые печатают результаты различных арифметических операций на этих значениях.
void print_sum(float x, float y) { std::cout << "The sum is " << x+y << std::endl; } void print_product(float x, float y) { std::cout << "The product is " << x*y << std::endl; } void print_difference(float x, float y) { std::cout << "The difference is " << x-y << std::endl; } void print_quotient(float x, float y) { std::cout << "The quotient is " << x/y << std::endl; }
Preferred syntax | Portable syntax |
---|---|
|
|
Эта программа распечатает следующее:
The sum is 8 The product is 15 The difference is 2 The quotient is 1.66667
Поэтому любые значения, которые даются sig
Когда он называется как функция, передается в каждый из слотов. Мы должны заранее объявлять типы этих значений, когда создаем сигнал. Тип boost::signal
означает, что сигнал имеет значение возврата void
и принимает два значения float
. Поэтому любой слот, подключенный к sig
, должен иметь возможность принимать два значения float
.
Так же, как слоты могут получать аргументы, они также могут возвращать значения. Затем эти значения могут быть возвращены обратно абоненту сигнала через комбинатор . Комбинатор - это механизм, который может принимать результаты слотов вызова (их много, а их нет или сотни; мы не знаем, пока программа не запустится) и объединять их в один результат, который будет возвращен абоненту. Единый результат часто является простой функцией результатов вызовов слота: результатом последнего вызова слота, максимальным значением, возвращаемым любым слотом, или контейнером всех результатов являются некоторые возможности.
Мы можем немного изменить наш предыдущий пример арифметических операций, чтобы все слоты возвращали результаты вычисления продукта, коэффициента, суммы или разницы. Затем сам сигнал может вернуть значение, основанное на этих результатах, для печати:
Preferred syntax | Portable syntax |
---|---|
float product(float x, float y) { return x*y; } float quotient(float x, float y) { return x/y; } float sum(float x, float y) { return x+y; } float difference(float x, float y) { return x-y; } |
float product(float x, float y) { return x*y; } float quotient(float x, float y) { return x/y; } float sum(float x, float y) { return x+y; } float difference(float x, float y) { return x-y; } |
В этом примере программа выводит 2
. Это связано с тем, что по умолчанию поведение сигнала, который имеет тип возврата (float
, первый аргумент шаблона, приведенный к шаблону класса boost::signal
), состоит в том, чтобы вызвать все слоты, а затем вернуть результат, возвращенный последним слотом. Это поведение, по общему признанию, глупо для этого примера, потому что слоты не имеют побочных эффектов, и в результате происходит последнее соединение слотов.
Более интересным результатом сигнала будет максимум значений, возвращаемых любым слотом. Для этого мы создаем пользовательский комбинатор, который выглядит следующим образом:
template<typename T> struct maximum { typedef T result_type; template<typename InputIterator> T operator()(InputIterator first, InputIterator last) const { // If there are no slots to call, just return the // default-constructed value if (first == last) return T(); T max_value = *first++; while (first != last) { if (max_value < *first) max_value = *first; ++first; } return max_value; } };
Шаблон класса maximum
действует как объект функции. Его тип результата задается параметром шаблона, и это тип, на котором он рассчитывает вычислить максимум на основе (например, maximum
найдет максимум float
в последовательности float
s). Когда максимум
Объект вызывается, ему дается последовательность итератора ввода [первый, последний]
, которая включает в себя результаты вызова всех слотов. maximum
использует эту последовательность входного итератора для расчета максимального элемента и возвращает это максимальное значение.
Мы фактически используем этот новый тип функционального объекта, устанавливая его в качестве комбинатора для нашего сигнала. Аргумент шаблона комбинатора следует за вызывающей подписью сигнала:
Preferred syntax | Portable syntax |
---|---|
|
|
Теперь мы можем подключить слоты, которые выполняют арифметические функции и используют сигнал:
sig.connect
("ient); sig.connect
(&product); sig.connect
(&sum); sig.connect
(&difference); std::cout << sig(5, 3) << std::endl;
Выход этой программы будет 15
, поскольку независимо от порядка, в котором соединены слоты, произведение 5 и 3 будет больше коэффициента, суммы или разницы.
В других случаях мы можем вернуть все значения, вычисленные слотами, в одну большую структуру данных. Это легко сделать с помощью другого комбинатора:
template<typename Container> struct aggregate_values { typedef Container result_type; template<typename InputIterator> Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); } };
Опять же, мы можем создать сигнал с этим новым комбинатором:
Preferred syntax | Portable syntax |
---|---|
|
|
Выход этой программы будет содержать 15, 8, 1,6667 и 2. Интересно, что первый аргумент шаблона для класса signal
, float
, на самом деле не является типом возврата сигнала. Вместо этого это тип возврата, используемый подключенными слотами, а также значение_тип
входных итераторов, передаваемых комбинатору. Комбинатор сам по себе является функциональным объектом, и его тип result_type
становится типом возврата сигнала.
Итераторы ввода, переданные комбинатору, преобразуют операции отсчета в вызовы слотов. Таким образом, комбинаторы могут использовать только некоторые слоты до тех пор, пока не будет выполнен определенный критерий. Например, в распределенной вычислительной системе комбинатор может задать каждой удаленной системе, будет ли она обрабатывать запрос. Только одна удаленная система должна обрабатывать конкретный запрос, поэтому после того, как удаленная система принимает работу, мы не хотим просить другие удаленные системы выполнить ту же задачу. Такой комбинатор должен только проверять значение, возвращенное при отмене ссылки на итератор, и возвращать, когда значение приемлемо. Следующий комбинатор возвращает первый указатель non-NULL в FulfilledRequest
Структура данных, не запрашивая последующие слоты для выполнения запроса:
struct DistributeRequest { typedef FulfilledRequest* result_type; template<typename InputIterator> result_type operator()(InputIterator first, InputIterator last) const { while (first != last) { if (result_type fulfilled = *first) return fulfilled; ++first; } return 0; } };
Ожидается, что слоты не будут существовать бесконечно после их подключения. Часто слоты используются только для получения нескольких событий, а затем отключаются, и программисту требуется контроль, чтобы решить, когда слот больше не должен быть подключен.
Точка входа для управления соединениями явно является boost::signals::connection
класс. Класс соединения
однозначно представляет соединение между конкретным сигналом и конкретным слотом. Способ connected()
проверяет, подключены ли сигнал и слот по-прежнему, а метод disconnect()
отключает сигнал и слот, если они подключены до его вызова. Каждый вызов в метод connect()
сигнала возвращает объект соединения, который может быть использован для определения того, существует ли соединение по-прежнему или для отключения сигнала и слота.
boost::signals::connection c = sig.connect
(HelloWorld()); if (c.connected
()) { // c is still connected to the signal sig(); // Prints "Hello, World!" } c.disconnect(); // Disconnect the HelloWorld object assert(!c.connected
()); c isn't connected any more sig(); // Does nothing: there are no connected slots
Слоты могут быть временно «блокированы», что означает, что они будут игнорироваться при вызове сигнала, но не были отключены. Функция block
временно блокирует слот, который можно разблокировать через unblock
. Вот пример блокировки/разблокировки слотов:
boost::signals::connection c = sig.connect
(HelloWorld()); sig(); // Prints "Hello, World!" c.block
(); // block the slot assert(c.blocked
()); sig(); // No output: the slot is blocked c.unblock
(); // unblock the slot sig(); // Prints "Hello, World!"
Класс boost::signals::scoped_connection
ссылается на соединение сигнал/слот, которое будет отключено, когда класс scoped_connection
выйдет из области действия. Эта способность полезна, когда соединение должно быть временным,
{
boost::signals::scoped_connection c = sig.connect
(ShortLived());
sig(); // will call ShortLived function object
}
sig(); // ShortLived function object no longer connected to sig
Можно отсоединить слоты, эквивалентные данному объекту функции, используя форму метода отсоединить
, при условии, что тип объекта функции имеет доступного оператора ==
. Например:
Preferred syntax | Portable syntax |
---|---|
void foo(); void bar(); signal<void()> sig; sig.connect(&foo); sig.connect(&bar); // disconnects foo, but not bar sig.disconnect(&foo); |
void foo(); void bar(); signal0<void> sig; sig.connect(&foo); sig.connect(&bar); // disconnects foo, but not bar sig.disconnect(&foo); |
Повышаю. Сигналы могут автоматически отслеживать время жизни объектов, участвующих в соединениях сигнал/слот, включая автоматическое отключение слотов, когда объекты, участвующие в вызове слота, разрушаются. Например, рассмотрим простую службу доставки новостей, где клиенты подключаются к поставщику новостей, который затем отправляет новости всем подключенным клиентам по мере поступления информации. Служба доставки новостей может быть построена следующим образом:
Preferred syntax | Portable syntax |
---|---|
class NewsItem { /* ... */ }; boost::signal<void (const NewsItem&)> deliverNews; |
class NewsItem { /* ... */ }; boost::signal1<void, const NewsItem&> deliverNews; |
Клиентам, которые хотят получать обновления новостей, необходимо только подключить функциональный объект, который может получать новости, к сигналу deliverNews
. Например, у нас может быть специальная область сообщений в нашем приложении специально для новостей, например:
struct NewsMessageArea : public MessageArea
{
public:
// ...
void displayNews(const NewsItem& news) const
{
messageText = news.text();
update();
}
};
// ...
NewsMessageArea newsMessageArea = new NewsMessageArea(/* ... */);
// ...
deliverNews.connect
(boost::bind(&NewsMessageArea::displayNews,
newsMessageArea, _1));
Однако что, если пользователь закроет область новостных сообщений, уничтожив newsMessageArea
Об этом сообщает 0>deliverNews. Скорее всего, произойдет сбой сегментации. Тем не менее, с Boost. Сигналы, которые нужно только сделать NewsMessageArea
trackable, а слот с участием newsMessageArea
будет отключен при уничтожении newsMessageArea
. Класс NewsMessageArea
отслеживается путем публичного извлечения из класса boost::signals::trackable
, например:
struct NewsMessageArea : public MessageArea, public boost::signals::trackable { // ... };
В настоящее время существует значительное ограничение на использование объектов trackable
при создании слот-соединений: функциональные объекты, построенные с использованием Boost. Привязка понимается таким образом, что указатели или ссылки на отслеживаемые
объекты, переданные boost::bind
, будут найдены и отслежены.
Предупреждение: определяемые пользователем функциональные объекты и функциональные объекты из других библиотек (например, Boost.Function или Boost.Lambda) не реализуют требуемые интерфейсы для обнаружения объектов trackable
, а будет молча игнорировать любые связанные отслеживаемые объекты. Будущие версии библиотек Boost устранят это ограничение.
Отключение сигнала/слота происходит при любом из этих условий:
Соединение явно отсоединяется с помощью метода отсоединить
прямо или косвенно с помощью метода отключить
сигнала или scoped_connect
.
A trackable
объект, связанный со слотом, разрушается.
Сигнал разрушается.
Эти события могут произойти в любое время без нарушения последовательности вызова сигнала. Если соединение сигнал/слот отключено в любое время во время последовательности вызова сигнала, последовательность вызова все еще будет продолжаться, но не вызовет отключенный слот. Кроме того, сигнал может быть уничтожен, пока он находится в последовательности вызовов, и в каком случае он завершит свою последовательность вызовов слота, но к ней нельзя получить прямой доступ.
Сигналы могут вызываться рекурсивно (например, сигнал A вызывает слот B, который вызывает сигнал A...). Поведение отключения не изменяется в рекурсивном случае, за исключением того, что последовательность вызова слота включает вызовы слота для всех вложенных вызовов сигнала.
Слоты на подъеме. Библиотека сигналов создается из произвольных функциональных объектов и поэтому не имеет фиксированного типа. Тем не менее, обычно требуется, чтобы слоты проходили через интерфейсы, которые не могут быть шаблонами. Слоты могут передаваться через slot_type
для каждого конкретного типа сигнала, и любой функциональный объект, совместимый с подписью сигнала, может передаваться по параметру slot_type
. Например:
Preferred syntax | Portable syntax |
---|---|
class Button
{
typedef boost::signal<void (int x, int y)> OnClick;
public:
void doOnClick(const OnClick::slot_type& slot);
private:
OnClick onClick;
};
void Button::doOnClick(
const OnClick::slot_type& slot
)
{
onClick.
|
class Button { typedef |
Метод doOnClick
теперь функционально эквивалентен методу connect
сигнала onClick
, но детали метода doOnClick
могут быть скрыты в файле деталей реализации.
Сигналы могут использоваться для реализации гибких архитектур Document-View. Документ будет содержать сигнал, к которому может подключиться каждая из точек зрения. Следующий класс Документ
определяет простой текстовый документ, который поддерживает нечеткие представления. Обратите внимание, что он хранит один сигнал, к которому будут подключены все виды.
class Document { public: typedef boost::signal<void (bool)> signal_t; typedef boost::signals::connection connection_t; public: Document() {} connection_t connect(signal_t::slot_function_type subscriber) { return m_sig.connect(subscriber); } void disconnect(connection_t subscriber) { subscriber.disconnect(); } void append(const char* s) { m_text += s; m_sig(true); } const std::string& getText() const { return m_text; } private: signal_t m_sig; std::string m_text; };
Далее мы можем определить базовый класс View
, из которого могут выводиться представления. Это не обязательно, но это отделяет логику Document-View от самой логики. Обратите внимание, что конструктор просто соединяет вид с документом, а деструктор отключает вид.
class View { public: View(Document& m) : m_document(m) { m_connection = m_document.connect(boost::bind(&View::refresh, this, _1)); } virtual ~View() { m_document.disconnect(m_connection); } virtual void refresh(bool bExtended) const = 0; protected: Document& m_document; private: Document::connection_t m_connection; };
Наконец, мы можем начать определять взгляды. Следующий класс TextView
обеспечивает простое представление текста документа.
class TextView : public View { public: TextView(Document& doc) : View(doc) {} virtual void refresh(bool bExtended) const { std::cout << "TextView: " << m_document.getText() << std::endl; } };
Альтернативно, мы можем предоставить представление документа, переведенного в шестнадцатеричные значения, используя вид HexView
:
class HexView : public View { public: HexView(Document& doc) : View(doc) {} virtual void refresh(bool bExtended) const { const std::string& s = m_document.getText(); std::cout << "HexView:"; for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) std::cout << ' ' << std::hex << static_cast<int>(*it); std::cout << std::endl; } };
Чтобы связать пример вместе, вот простая функция main
, которая устанавливает два представления, а затем изменяет документ:
int main(int argc, char* argv[]) { Document doc; TextView v1(doc); HexView v2(doc); doc.append(argc == 2 ? argv[1] : "Hello world!"); return 0; }
Полный примерный источник, предоставленный Китом Макдональдом, доступен в libs/signals/example/doc_view.cpp
.
Часть подъема. Библиотека сигналов компилируется в бинарную библиотеку, которая должна быть связана с вашим приложением для использования сигналов. Пожалуйста, обратитесь к руководству Getting Started. Вам нужно будет связать с библиотекой boost_signals
.
Статья Tutorial раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 32. Boost.Signals может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: Chapter 32. Boost.Signals ::
реклама |