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