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

  void setup(Executor* argCallbackClass, ptr_ callback_method argCallbackMethod);

private:

  Executor* ptrCallbackClass = nullptr;

  ptr_ callback_method ptrCallbackMethod = nullptr;

};

//Functional object

class Initiator4

{

public:

  void setup(const CallbackHandler& callback);

private:

  CallbackHandler callbackObject;

};

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

Листинг 37. Шаблон для инициатора асинхронного вызова

template<typename CallbackArgument>

class Initiator

{

public:

  void setup(const CallbackArgument& argument)

  {

      callbackHandler = argument;

  }

  void run()

  {

    int eventID = 0;

    //Some actions

    callbackHandler(eventID);

  }

private:

  CallbackArgument callbackHandler;

};

Получившийся шаблон подходит для реализации с использованием функционального объекта. Для реализаций с использованием указателей на функцию, указателей на статический метод и на метод-член класса можно использовать шаблон для преобразования вызовов (см. п. 4.2.2). А вот реализация с помощью лямбда-выражений здесь работать не будет, потому что хранить лямбда-выражение как аргумент, подобно обычной переменной, нельзя. Рассмотрим этот вопрос подробнее.

4.4.2. Хранение лямбда-выражений

Почему хранение лямбда-выражений является проблемой?

При объявлении лямбда-выражения компилятор генерирует функциональный объект, который называется объект-замыкание (closure type). Этот объект хранит в себе захваченные переменные и имеет перегруженный оператор вызова функции. Сигнатура оператора повторяет сигнатуру лямбда-выражения, а в теле оператора размещается код выражения. Пример объекта-замыкания приведен в Листинг 38.

Листинг 38. Лямбда-выражение и объект-замыкание

int main()

{

  int capture = 0;

  [capture](int eventID) {/*this is a body of lambda*/};

  //The following object will be generated implicitly by the compiler from lambda declaration

  class Closure

  {

  public:

    Closure(int value) :capture(value) {}

    void operator() (int eventID)

    {

      /*this is a body of lambda*/

    }

    int capture; //captured value

  };

}

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

Если лямбда-выражение не захватывает переменные, то стандарт допускает преобразование лямбда-выражения к указателю на функцию. В этом случае объект-замыкание не содержит переменных, что позволяет код лямбда-выражения оформить в виде статической функции и объявить соответствующий оператор преобразования. Таким образом, появляется возможность сохранить лямбда-выражение в переменной типа "указатель на функцию", как показано в Листинг 39.

Листинг 39. Объект-замыкание с преобразованием в указатель на функцию

int main()

{

  [](int eventID) {/*this is a body of lambda*/};  // (1)

  //The following object will be generated implicitly by the compiler from lambda declaration

  class Closure  // (2)

  {

  public:

    void operator() (int eventID)  // (3)

    {

      call_invoker(eventID);

    }

    static void  call_invoker(int eventID) { /*this is a body of lambda*/ }  // (4)

    using function_pointer = void(*)(int);  // (5)

    operator function_pointer() const       // (6)

    {

      return call_invoker;

    }

  };

  //Conversion the closure object to the function pointer

  Closure cl;  // (7)

  using pointer_to_function = void(*)(int);  // (8)

  pointer_to_function fptr = cl;             // (9)

  //Conversion a lambda to the function pointer

 fptr = [](int eventID) {/*this is a body of lambda*/};  // (10)

}

В строке 1 объявлено лямбда-выражение, в строке 2 объявлен объект-замыкание. Подчеркнем: этот объект здесь всего лишь для демонстрации, чтобы показать, как он будет сгенерирован компилятором. В реальном коде такой объект объявлять не нужно, компилятор его создаст при объявлении лямбда-выражения.