Выбрать главу

    }

  private:

    ArgType storedArgument;   // (9)

  };

public:

  void operator() (int value)  // (10)

  {

    callablePointer->operator()(value);  // (11)

  }

  template <typename ArgType>

  void operator = (ArgType argument)  // (12)

  {

    callablePointer.reset(new CallableObject<ArgType>(argument));  // (13)

  }

};

В строке 1 объявлен класс, реализующий универсальный аргумент. В строке 2 объявлен класс, который будет использоваться в качестве базового.

В базовом классе перегружен оператор вызова функции 3. Оператор объявлен чисто виртуальным, чтобы опустить его реализацию. Предполагается, что этот оператор будет выполнять обратный вызов, но аргумента вызова здесь нет, он будет храниться в наследуемом классе. Таким образом, реализация смысла не имеет. Более того, если, допустим, нам понадобится, чтобы оператор возвращал результат, то в нем должна присутствовать команда return, и какое тогда возвращать значение?

В строке 4 объявлен указатель на базовый класс, объявленный в 2.

В строке 5 объявлен шаблонный класс, который будет хранить переданный аргумент и вызывать его. Переменная для хранения аргумента объявлена в строке 9, тип переменной задается параметром шаблона. Аргумент назначается в конструкторе 6. Также в этом классе переопределяется оператор вызова функции 7, в котором происходит обратный вызов 8 через сохраненный аргумент.

В строке 10 объявлен перегруженный оператор основного класса, в котором вызывается соответствующий переопределенный оператор через указатель на базовый класс (строка 11).

В строке 12 объявлен шаблонный оператор присваивания, который настраивает аргумент. В реализации этого оператора 13 создается новый класс CallableObject нужного типа, в конструкторе этого класса переданный аргумент сохраняется, после чего переназначается указатель. Таким образом, при вызове оператора 10 будет вызван оператор соответствующего класса 11, и последний осуществит вызов через сохраненный аргумент.

Можно заметить, что универсальный аргумент не выполняет вызов сам по себе. По сути, он является своего рода оболочкой, которая перенаправляет вызов соответствующему объекту. Таким образом, у нас появляется новое понятие – объект вызова.

Объект вызова – это некоторая конструкция C++, поддерживающая интерфейс вызова в формате функции.

В соответствии с стандартом C++ на сегодняшний день23, в качестве объектов вызова могут использоваться следующие конструкции:

• функции;

• методы класса;

• классы с перегруженным оператором вызова функции;

• лямбда-выражения.

В реализациях инициатора с помощью шаблонов, рассмотренных в предыдущих главах (см. п. 4.2.1, 4.4.1), аргумент вызова совпадает с объектом вызова. При использовании универсального аргумента эти сущности будут различаться: универсальный аргумент хранит в себе объект вызова.

Итак, мы реализовали универсальный аргумент, продемонстрируем теперь, как он может использоваться для реализации обратных вызовов (Листинг 46).

Листинг 46. Использование универсального аргумента

class Executor

{

public:

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

  void callbackHandler(int eventID) {}

  void operator() (int eventID) {}

};

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

int main()

{

UniArgument argument;

Executor executor;

int capturedValue = 0;

using PtrExtFunc = void(*) (int, void*);

argument = CallbackConverter<PtrExtFunc, void*>(ExternalHandler, &executor);                          // (1)

using PtrStaticMethod = void(*) (int, Executor*);

argument = CallbackConverter<PtrStaticMethod, Executor*>(Executor::staticCallbackHandler, &executor);  //(2)

using PtrMemberMethod = void(Executor::*)(int);

argument = CallbackConverter<PtrMemberMethod, Executor>(&Executor::callbackHandler, &executor);       // (3)

argument = executor;  // (4)

argument = [capturedValue](int eventID) {/*Body of lambda*/};  // (5)

}

В строке 1 аргументу присваивается указатель на функцию, для преобразования вызовов используется класс CallbackConverter из Листинг 27 п. 4.2.2. Этот класс инстанциируется соответствующими типами, в конструкторе ему передается функция ExternalHandler и контекст, в качестве которого выступает указатель на класс Executor.

В строке 2 аргументу присваивается указатель на статический метод класса, что, в общем-то, идентично рассмотренному предыдущему случаю.

В строке 3 аргументу присваивается указатель на метод-член класса, для преобразования вызовов используется класс CallbackConverter из Листинг 28 п. 4.2.2. Этот класс инстанциируется соответствующими типами, в конструкторе ему передается указатель на класс и указатель на метод класса.

вернуться

23

На момент написания книги это C++ 20.