Выбрать главу
Листинг 61. Перенаправление вызовов для методов-членов класса

#include <functional>

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