Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
4.6.6. Перенаправление вызовов
Представьте следующую ситуацию: инициатор вызывает функцию с одной сигнатурой, а в клиенте реализован обработчик с другой сигнатурой. Например, в исполнителе реализована функция обработки нажатия кнопки, которая на вход принимает два параметра – идентификатор кнопки и текущее поле редактирования. В то же время инициатор вызывает функцию, передавая ей только один аргумент – идентификатор текущей нажатой кнопки, и он ничего не знает об остальных элементах управления. Можно ли сделать так, чтобы инициатор вызывал одну функцию, но при этом бы вызывалась другая функция, другими словами, происходило перенаправление вызова? В стандартной библиотеке для этого существуют специальные объекты связывания std::bind, которые при необходимости могут сохраняться в std::functionподобно обычным функциональным объектам.
Графически использование связывания продемонстрировано на Рис. 18. Пусть инициатор вызывает функцию 1, которая на вход принимает аргумент 1. Исполнитель реализует обратный вызов с помощью функции 2, которая принимает на вход два аргумента. Вместо функции 1 инициатору назначается объект связывания, который имеет перегруженный оператор вызова функции с сигнатурой 1. Указанный объект хранит дополнительный параметр, значение которому присваивается во время инициализации. Перегруженный оператор, в свою очередь, вызывает функцию 2, первому аргументу передает сохраненный параметр, а второму аргументу передает значение аргумента из функции 1. Таким образом, осуществляется перенаправление вызова из функции 1 в функцию 2.

Рис. 18. Перенаправление вызовов
Использование перенаправления вызовов представлено в Листинг 60.
#include
void NativeHandler(int eventID) // (1)
{
//here eventID is 10
}
void AnotherHandler(int contextID, int eventID) // (2)
{
//here eventID is 1, contextID is 10;
}
int main()
{
int eventID = 1; int contextID = 10;
std::function fnt; // (3)
fnt = NativeHandler; // (4)
fnt(eventID); // (5) NativeHandler will be called
fnt = std::bind(AnotherHandler, contextID, std::placeholders::_1); // (6)
fnt(eventID); // (7) AnotherHandler will be called
}
В строке 1 объявлен исходный обработчик, в строке 2 – обработчик, в который будет перенаправляться вызов. В строке 3 объявлен универсальный аргумент с исходной сигнатурой. В строке 4 аргументу назначена функция, которая будет вызвана при выполнении вызова 5.
В строке 6 вызывается функция bind, которая из переданных аргументов формирует объект связывания. На вход std::bindпередается имя новой функции-обработчика и аргументы, которые будут передаваться в эту функцию. Первому аргументу здесь будет назначено значение contextID, а второму аргументу будет назначено значение 1-го по порядку аргумента из исходной функции. Здесь конструкция std::placeholdersопределяет номер аргумента в исходной функции, который будет подставлен в качестве аргумента в перенаправляемую функцию.
Сформированный объект связывания сохраняется в универсальном аргументе. Если мы теперь выполним вызов (строка 7), то будет вызвана функция, назначенная этому объекту, и этой функции будут переданы соответствующие аргументы.
Аналогичным образом может быть объявлено перенаправление вызовов для методов-членов класса, но здесь должно соблюдаться следующее правило: первому аргументу новой функции должен быть назначен первый аргумент исходной функции, потому что он определяет экземпляр класса, для которого вызывается метод. Пример приведен в Листинг 61.
#include
class CallbackHandler
{
public:
void NativeHandler(int eventID)
{
//eventID = 1;
}
void AnotherHandler(int contextID, int eventID)
{
//eventID = 1, contextID = 10;
}
};
int main()
{
using namespace std::placeholders; // (1)
int eventID = 1; int contextID = 10;
CallbackHandler handler;
std::function fnt;
fnt = &CallbackHandler::NativeHandler;
fnt(&handler, eventID); // NativeHandler will be called
fnt = std::bind(&CallbackHandler::AnotherHandler, _1, contextID, _2); // (2)
fnt(&handler, eventID); // AnotherHandler will be called
}
Здесь в строке 1 мы использовали using namespace, что сокращает объявление позиций аргументов: как видно из строки 2, мы сразу пишем позицию без использования std::placeholders, что значительно компактнее и проще для восприятия. Здесь в исходной функции присутствует неявный параметр с номером 1, который определяет экземпляр класса. Этот параметр назначается первому (неявному) параметру новой функции, а второй параметр исходной функции eventIDназначается последнему параметру новой функции.
В общем случае могут быть 4 варианта перенаправления вызовов:
• из функции в функцию (пример в Листинг 60);
• из функции в метод класса;
• из метода класса в другой метод этого же класса (пример в Листинг 61);
• из метода класса в метод другого класса;
• из метода класса в функцию.
Реализация указанных вариантов, по сути, одинакова, отличаются только объявления связывания. Сведем эти объявления в таблицу (Табл. 13).
Табл. 13. Связывания для различных вариантов перенаправления вызовов.

Теперь перенаправление вызовов в исполнителе не представляет сложности: при настройке вместо объекта вызова нужно всего лишь подставить необходимое связывание. Пример для варианта «функция – функция» приведен в Листинг 62, здесь используется инициатор из Листинг 53.
void NativeHandler(int eventID)
{
//here eventID is 10
}
void AnotherHandler(int contextID, int eventID)
{
//here eventID is 10, contextID is 1;
}
int main()
{
int eventID = 10; int contextID = 1;
Initiator initiator; // (1)
initiator.setup(NativeHandler); // (2)
initiator.setup(std::bind(AnotherHandler, contextID, std::placeholders::_1)); // (3)
initiator.run(); // (4)
}
В строке 1 объявлен инициатор. В строке 2 происходит настройка инициатора с передачей ему указателя на функцию с «родной» сигнатурой, т. е. сигнатурой, для которой инициатор осуществляет вызов. Если бы мы после этого запустили инициатор путем вызова метода run, то инициатор вызывал бы функцию NativeCallbackHandler. В строке 3 вместо функции с «родной» сигнатурой мы подставляем объект связывания, который будет перенаправлять вызов в другую функцию. В строке 4 запускаем инициатор, в котором после вызова функции объекта связывания будет осуществлен вызов AnotherCallbackHandlerс соответствующими параметрами. Аналогичным образом, подставляя нужные связывания из Табл. 13, осуществляется перенаправление вызовов для других вариантов.
Читать дальшеИнтервал:
Закладка: