Добавим в реализацию инициатора, описанного в Листинг 37 п. 4.4.1, два конструктора. Один конструктор будет с переменной аргументом обратного вызова для инициализации члена класса. Другой конструктор будет без аргументов (конструктор по умолчанию), чтобы оставить возможность отложенной настройки (Листинг 41).
Листинг 41. Инициатор с дополнительными конструкторамиtemplate<typename CallbackArgument>
class Initiator
{
public:
Initiator() {}
Initiator(const CallbackArgument& argument) : callbackHandler(argument) {}
void setup(const CallbackArgument& argument)
{
callbackHandler = argument;
}
void run()
{
int eventID = 0;
//Some actions
callbackHandler(eventID);
}
private:
CallbackArgument callbackHandler;
};
Для любых типов аргументов обратного вызова, кроме лямбда-выражений, допускается использование обоих конструкторов. Для лямбда-выражений допускается использование только конструктора с аргументом, при попытке использования конструктора по умолчанию компилятор выдаст ошибку. Кроме того, в этом случае нельзя будет вызвать метод setup также будет сгенерирована ошибка. Таким образом, использование инициатора с лямбда-выражением не предполагает динамической модификации: настройка происходит один раз в конструкторе при инстанциировании шаблона, и больше изменить ее нельзя20.
А какой тип аргумента нам указывать при инстанциировании шаблона, ведь тип лямбда-выражения является анонимным? Для этой цели мы будем использовать ключевое слово decltype, которое возвращает тип объявленной переменной (см. Листинг 42).
Листинг 42.Инстанциирование шаблона асинхронного обратного вызова для лямбда-выраженияint capture = 10;
auto lambda = [capture](int eventID) {/*this is a body of lambda*/};
Initiator<decltype(lambda)> callbackLambda1 (lambda); // Ok, initialization in constructor
Initiator<decltype(lambda)> callbackLambda = lambda; // Ok, implicit constructor call
Initiator<decltype(lambda)> callbackLambda2; //Error: attempting to reference a deleted function
callbackLambda.setup(lambda); //Error: operator = attempting to reference a deleted function
callbackLambda.run();
4.4.3. Исполнитель
В Листинг 43 приведены примеры реализации исполнителя для различных типов аргументов. Объявления класса CallbackConverter представлены в Листинг 27 и Листинг 28 п. 4.2.2, инициатор используется из Листинг 41 п. 4.4.2.
Листинг 43. Исполнитель для шаблона-инициатора с различными типами аргументаclass Executor // (1)
{
public:
static void staticCallbackHandler(int eventID, Executor* executor) {}
void callbackHandler(int eventID) {}
void operator() (int eventID) {}
};
void ExternalHandler(int eventID, void* somePointer) {} // (2)
int main()
{
Executor executor; // (3)
int capturedValue = 0;
// (4) Pointer to the external function
using PtrExtFunc = void(*) (int, void*); // (5)
using CallbackExtFunction = CallbackConverter<PtrExtFunc, void*>; // (6)
Initiator<CallbackExtFunction> initExtFunction; // (7)
initExtFunction.setup(CallbackExtFunction(ExternalHandler, &executor)); // (8)
// (9) Pointer to the static method
using PtrStaticMethod = void(*) (int, Executor*); // (10)
using CallbacStaticMethod = CallbackConverter<PtrStaticMethod, Executor*>; // (11)
Initiator<CallbacStaticMethod> initStaticMethod; // (12)
initStaticMethod.setup(CallbacStaticMethod(Executor::staticCallbackHandler, &executor)); // (13)
// (14) Pointer to the class member method
using PtrMethod = void(Executor::*)(int); // (15)
using CallbackMemberMethod = CallbackConverter<Executor, void(Executor::*)(int)>; // (16)
Initiator<CallbackMemberMethod> initMemberMethod; // (17)
initMemberMethod.setup(CallbackMemberMethod(&executor, &Executor::callbackHandler)); // (18)
// (19) Functional object
Initiator<Executor> initFunctionObject; // (20)
initFunctionObject.setup(executor); // (21)
// (22) Lambda-expression
auto lambda = [capturedValue](int eventID) {/*Body of lambda*/}; // (23)
Initiator<decltype(lambda)> initLambda ( lambda); // (24)
}
В строке 1 объявлен класс исполнитель, в котором определены необходимые нам типы вызовов: статический метод, метод-член, перегруженный оператор. В строке 2 объявлена внешняя функция, в строке 3 экземпляр исполнителя.
В строке 4 показан обратный вызов через указатель на функцию. Объявлен тип указателя на функцию 5, тип функционального объекта для преобразования вызова 6, инстанциирование шаблона инициатора соответствующим типом 7, настройка инициатора 8. Запуск инициатора (метод run) не показан, чтобы не загромождать описание.
В строке 9 показан обратный вызов через указатель на статический метод класса. Похоже на предыдущий случай, только в качестве контекста используется указатель на класс. Объявлен тип указателя на статический метод 10, тип функционального объекта для преобразования вызова 11, инстанциирование инициатора соответствующего типа 12, настройка инициатора 13.
В строке 14 показан обратный вызов через указатель на метод-член класса. Объявлен тип указателя на метод 15, тип функционального объекта для преобразования вызова 16, инстанциирование инициатора соответствующим типом 17, настройка инициатора 18.
В строке 19 показан обратный вызов с помощью функционального объекта. Инстанциирование инициатора объявлено в строке 20, настройка инициатора в строке 21.
В строке 22 показан обратный вызов с помощью лямбда-выражения. В строке 23 объявлено лямбда-выражение, которое запоминается в соответствующей переменной. В строке 24 инстанциирован инициатор типом лямбда-выражения. Инициатору в конструкторе передается переменная объект указанного выражения.
Для случаев, когда используется преобразование вызовов (объявления 4, 9 и 14), можно использовать сокращенные объявления без использования промежуточных деклараций. Код в этом случае получается более компактным, но менее понятным (см. Листинг 44).
Листинг 44. Компактный способ объявлений при использовании преобразования вызововint main
{
Executor executor;
// (4) Pointer to the external function
Initiator<CallbackConverter<void(*)(int, void*), void*>> initExtFunction;
initExtFunction.setup(CallbackConverter<void(*)(int, void*), void*>(ExternalHandler, &executor));
// (9) Pointer to the static method
Initiator<CallbackConverter<void(*)(int, Executor*), Executor*>> initStaticMethod;
initStaticMethod.setup(CallbackConverter<void(*)(int, Executor*), Executor*>(Executor::staticCallbackHandler, &executor));
// (14) Pointer to the class member method
Initiator<CallbackConverter<Executor, void(Executor::*)(int)>> initMemberMethod;
initMemberMethod.setup(CallbackConverter<Executor, void(Executor::*)(int)>(&executor, &Executor::callbackHandler));
}
Итак, как мы видим, для каждого типа аргумента обратного вызова нам приходится объявлять соответствующий инициатор. Может быть, можно сделать так, чтобы инициатор умел работать с различными типами аргументов? Для этого нужно спроектировать универсальный аргумент, чем мы и займемся в следующей главе.
4.5. Универсальный аргумент
4.5.1. Динамический полиморфизм
Для реализации универсального аргумента прежде всего необходимо обеспечить динамический полиморфизм, т. е. аргумент должен изменять свой тип в зависимости от задаваемого значения21.
Как решается указанная задача в объектно-ориентированном дизайне? Объявляется базовый абстрактный класс, в котором описывается интерфейс в виде набора чисто виртуальных методов. Новый тип создается путем создания наследуемого класса, в котором объявляются нужные переменные и переопределяются методы. При инициализации создается класс нужного типа, и он сохраняется в переменной указателе на базовый класс. Мы будем использовать аналогичный подход, только наследуемые типы будут создаваться динамически, используя параметры шаблона. Указанная техника называется «стирание типов»: при назначении нового типа аргумента предыдущий сохраненный уничтожается, и его место занимает новый22.
Графическое изображение стирания типов изображено на Рис. 17. Рассмотрим начальное состояние а), показанное в верхней части рисунка. Имеется некоторый класс, назовем его UniArgument. В этом классе объявлен перегруженный оператор вызова функции 2. Также здесь имеется указатель 3 типа Callable*, который указывает на соответствующий экземпляр класса Callable. Класс Callable4 объявлен внутри UniArgument и имеет виртуальный перегруженный оператор вызова функции с пустой реализацией.
Когда в UniArgument происходит вызов 1 перегруженного оператора 2, последний через указатель 3 вызывает виртуальный перегруженный оператор класса Callable.