Обратные вызовы в C++ - Виталий Евгеньевич Ткаченко 17 стр.


void External(int eventID) {};


int main()

{

  struct Call

  {

    void operator() (int eventID) {};

  } objectCall;


  std::function<void(int)> fnt;


  fnt = External;

  fnt = objectCall;

  fnt = [](int evetID) {};


  fnt(0);

}


Полезной особенностью std::function является проверка настройки объекта вызова. Если объект не настроен, т. е. не было ни одного присваивания, то при попытке вызова будет выброшено исключение. Проверить, настроен ли объект, можно с помощью перегруженного оператора bool, пример приведен в Листинг 52.

Листинг 52. Проверка настройки аргумента

int main()

{

  std::function<void(int)> fnt;


  fnt(0); //Error: argument is not set. Exception will be thrown


  fnt = [](int) {};

  fnt(0); //Ok, argument is set


  //Check if the argument is set

  if (fnt)

  {

    fnt(0);

  }

}

4.6.2. Инициатор с универсальным аргументом

Для реализации инициатора с универсальным аргументом необходимо для хранения аргумента объявить соответствующую класс-оболочку std::function (Листинг 53).

Листинг 53. Инициатор с оболочкой std::function

class Initiator  // (1)

{

public:

  template<typename CallbackArgument>

  void setup(const CallbackArgument& argument)  // (2)

  {

    callbackHandler = argument;

  }


  void run()

  {

  int eventID = 0;

  //Some actions

  callbackHandler(eventID);

  }


private:

  std::function<void(int)> callbackHandler;  // (3)

};


Если сравнить реализацию инициатора с фиксированным типом аргумента (Листинг 37 п. 4.4.1) с приведенной, то можно заметить следующие отличия. В первом случае инициатор является шаблоном, здесь он объявляется обычным способом. Далее, хранимый аргумент 3 не является переменной типа, задаваемого параметром шаблона, он объявлен как универсальный аргумент std::function. Метод настройки 2 объявлен как шаблон, параметром которого является тип назначаемого аргумента.

Описанный инициатор не работает с указателями на функцию и на метод класса: в первом случае необходимо передавать контекст, во втором случае необходимо передавать указатель на экземпляр класса и использовать другой синтаксис для вызова. Как уже рассматривалось в п. 4.2.2, в этих случаях необходимо преобразование вызовов. Однако, поскольку в универсальном аргументе сигнатура может настраиваться, в объекты преобразования также нужно ввести поддержку настройки сигнатуры.

4.6.3. Преобразование с настройкой сигнатуры

В п. 4.2.2 реализованы объекты преобразования, которые работали с фиксированной сигнатурой. Используя технику, описанную в Листинг 47 п. 4.5.2, модифицируем их таким образом, чтобы сигнатуру можно было настроить. Для этого в параметрах шаблона вместо задания типов указателей на функцию будем задавать параметры, определяющие сигнатуру, а типы указателей будем выводить из этих параметров.

Рассмотрим вначале указатели на функцию (Листинг 54).

Листинг 54. Преобразование вызовов с настройкой сигнатуры для указателей на функцию

template<typename unused>  // (1)

class CallbackConverter;


template<typename Context, typename Return, typename  ArgumentList>  // (2)

class CallbackConverter<Return(Context, ArgumentList)>               // (3)

{

public:


  using Function = Return(*)(Context, ArgumentList);  // (4)


  CallbackConverter(Function argFunction = nullptr, Context argContext = nullptr)  // (5)

  {

    ptrFunction = argFunction; context = argContext;

  }


  Return operator() (ArgumentList arguments)                 // (6)

  {

    ptrFunction(context, arguments);                         // (7)

  }

private:

  Function ptrFunction;  // (8)

  Context context;       // (9)

};


В строке 1 вводится общая специализация шаблона. В строке 2 объявляется специализация для указателей на функцию, в которой задается тип передаваемого контекста и параметры сигнатуры. В строке 4 выводится тип указателя. В конструкторе 5 осуществляется настройка указателей. В перегруженном операторе 6 осуществляется вызов 7, в который передаются соответствующие аргументы.


Аналогично выполняется специализация для вызова методов класса (Листинг 55).

Аналогично выполняется специализация для вызова методов класса (Листинг 55).

Листинг 55. Преобразование вызовов с настройкой сигнатуры для указателей на метод класса.

template<typename ClassType, typename Return, typenameArgumentList>  // (1)

class CallbackConverter<Return(ClassType::*)(ArgumentList)>          // (2)

{

public:


  using MemberPointer = Return(ClassType::*)(ArgumentList);  // (3)


  CallbackConverter(MemberPointer methodPointer = nullptr, ClassType* classPointer = nullptr)  // (4)

  {

      ptrClass = classPointer; ptrMethod = methodPointer;

  }


  Return operator()(ArgumentList arguments)  // (5)

  {

    (ptrClass->*ptrMethod)(arguments);       // (6)

  }

private:

  ClassType* ptrClass;                          // (7)

  MemberPointer ptrMethod;                      // (8)

};


Реализация практически повторяет предыдущую, за исключением того, что в объявлениях типов сигнатуры добавляется класс (строки 2 и 3), а перегруженный оператор вызывает метод класса (строка 6).

4.6.4. Исполнитель

Реализация исполнителя для инициатора с универсальным аргументом (см. Листинг 53 п. 4.6.2) приведена в Листинг 56, здесь используется CallbackConverter из Листинг 54 п. 4.6.3.

Листинг 56. Исполнитель для инициатора с оболочкой std::function

class Executor

{

public:

  static void staticCallbackHandler(Executor* executor, int eventID) {}

  void callbackHandler(int eventID) {}

  void operator() (int eventID) {}

};


void ExternalHandler(void* somePointer, int eventID) {}


int main()

{

  int capturedValue = 0;

  Initiator initiator;

  Executor executor;


  // Pointer to the external function

  initiator.setup(CallbackConverter<void(void*, int)>(ExternalHandler, &executor));


  // Pointer to the static method

  initiator.setup(CallbackConverter<void(Executor*, int)>(Executor::staticCallbackHandler, &executor));


  // Pointer to the class member method

  initiator.setup(CallbackConverter<void(Executor::*)(int)>(&Executor::callbackHandler, &executor));


  // Functional object

  initiator.setup(executor);


  // Lambda-expression

  initiator.setup([capturedValue](int eventID) {});

}


Если сравнить приведенную реализацию исполнителя для шаблона-инициатора с фиксированным типом аргумента (Листинг 43 и Листинг 44 п. 4.4.3) с приведенной, то можно заметить следующее. В первом случае для каждого типа аргумента приходится объявлять отдельный инициатор, инстанциируя его соответствующим типом. Здесь инициатор объявляется один раз, после чего тип аргумента вызова настраивается в процессе выполнения программы. В результате упрощается разработка, улучшается гибкость и прозрачность кода.

4.6.5. Инициатор для методов класса

До сих пор для вызова методов класса мы использовали преобразование вызовов. Однако, поскольку std::function непосредственно поддерживает вызов методов, появляется возможность реализовать специализированный инициатор для указанного случая. За основу возьмем инициатор из п. 4.6.2 и модифицируем его.

Как мы видели в реализации универсального аргумента (п. 4.5.3), для вызова метода класса первым параметром должен передаваться указатель на экземпляр класса. Поэтому, в инициатор необходимо добавить переменную для хранения этого указателя. Но поскольку тип класса заранее неизвестен, его следует задавать как параметр, т. е. инициатор должен быть объявлен в виде шаблона. Далее необходимо добавить метод для настройки указателя и, соответственно, при задании сигнатуры и выполнении вызова передавать дополнительный аргумент указатель на экземпляр класса. Реализация приведена в Листинг 57.

Листинг 57. Инициатор с оболочкой std::function для вызова методов класса

template<typename ClassName>  // (1)

class InitiatorForClass

{

public:

  template<typename CallbackArgument>

  void setup(const CallbackArgument argument)  // (2)

  {

    callbackHandler = argument;

  }


  void setupInstance (ClassName* classObject)  // (3)

  {

    ptrClass = classObject;

  }


  void run()  // (4)

  {

      int eventID = 0;

      //Some actions

      callbackHandler(ptrClass, eventID);  // (5)

  }


private:

  std::function<void(ClassName*, int)> callbackHandler;  // (6)

  ClassName* ptrClass = nullptr;                         // (7)

};

Назад Дальше