Обратные вызовы в C++ | страница 58



>}


>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) на этапе компиляции тип аргумента неизвестен, поэтому код обработки не может быть встроен в точку вызова.

Первые три причины вносят незначительный вклад в общее время, затрачиваемое на выполнение вызова, а вот четвертая может привести к резкому падению производительности. Мы уже рассматривали подобную проблему при анализе функциональных объектов (п. 2.4.6): при малом объеме кода обработчика время, затраченное на вызов функции, может превысить время выполнения тела функции.