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

Итак, универсальный аргумент практически готов. Нам осталось реализовать оператор копирования, оператор присваивания и некоторые другие операции. Но мы этим заниматься не будем: разработчики стандартной библиотеки уже обо всем позаботились, поэтому темой следующей главы будет обзор инструментов STL для организации обратных вызовов24.

4.6. Использование стандартной библиотеки

4.6.1. Организация вызовов

В стандартной библиотеке имеется полиморфный класс – оболочка std::function, предназначенная для организации вызовов различных типов. Этот класс идеально подходит на роль универсального аргумента. Кроме рассмотренных техник стирания типа и настройки сигнатуры, в нем реализовано множество других вещей: конструктор копирования, оператор присваивания, поддержка указателей на методы класса, проверка настройки аргумента, локальный буфер для хранения аргумента и многое другое. Мы не будем рассматривать реализацию std::function, потому что, во-первых, она достаточно сложная, а, во-вторых, может изменяться в зависимости от версии и платформы. При желании читатель сможет сделать это самостоятельно, проанализировав исходный код, мы же сосредоточимся на практическом использовании класса-оболочки.

Насколько сложна реализация std::function, настолько же просто ее использование. По аналогии с универсальным аргументом, рассмотренном в предыдущей главе, достаточно объявить экземпляр класса с нужной сигнатурой, после чего ему можно назначать различные объекты вызовов (Листинг 51).

Листинг 51. Использование std::function

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

вернуться

24

«Зачем же мы тогда разрабатывали универсальный аргумент, если в STL все уже давно реализовано?» – может воскликнуть рассерженный читатель. Ну, во-первых, грамотный разработчик отличается от обычного разработчика тем, что он не только знает, как применять те или иные инструменты, но еще и понимает, как они работают. И, во-вторых, рассмотренные методы используются не только в проектировании обратных вызовов, они могут использоваться при решении самых различных задач.