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.
Листинг 62. Перенаправление вызовов в исполнителе
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, осуществляется перенаправление вызовов для других вариантов.
Итак, использование объектов связывания предлагает универсальный способ преобразования вызовов: вместо объектов преобразования (п. 4.2.2, 4.6.3) в универсальный аргумент подставляется объект связывания, сгенерированный соответствующим вызовом std::bind.
4.6.7. Универсальный аргумент и производительность
Может показаться, что организация обратных вызовов с использованием std::functionв качестве универсального аргумента является наилучшим решением, предлагающим простоту реализации в сочетании с максимальной гибкостью. В большинстве случаев это действительно так, однако std::functionобладает недостатком, который может свести на нет все остальные достоинства: большие временные затраты для осуществления вызова по сравнению с другими способами реализации. Причины этого следующие:
1) при вызове происходит проверка, настроен ли аргумент;
2) вызов происходит через промежуточный объект с виртуальной функцией (см. 4.5.1) – расходуется дополнительное время для вызова этой функции;
3) поскольку промежуточный объект создается динамически, его адрес может изменяться, что требует загрузки адреса перед вызовом;
4) на этапе компиляции тип аргумента неизвестен, поэтому код обработки не может быть встроен в точку вызова.
Читать дальше