Использование универсального аргумента для вызова метода класса представлено в Листинг 50.
struct CallbackHandler
{
void handler1(int eventID) {};
bool handler2(int eventID, int contextID) { return false; };
};
int main()
{
CallbackHandler callbackObject;
UniArgument
UniArgument
argument1 = &CallbackHandler::handler1; // (3)
argument2 = &CallbackHandler::handler2; // (4)
argument1(&callbackObject, 100); // (5)
argument2(&callbackObject, 0, 1); // (6)
}
В строках 1 и 2 объявлены универсальные аргументы для вызова соответствующих методов класса. Как видим, в сигнатуре функции первый параметр является типом класса, для которого будут вызываться соответствующие методы. В строках 3 и 4 производится настройка методов, в строках 5 и 6 – вызовы методов для экземпляра соответствующего класса.
Итак, универсальный аргумент практически готов. Нам осталось реализовать оператор копирования, оператор присваивания и некоторые другие операции. Но мы этим заниматься не будем: разработчики стандартной библиотеки уже обо всем позаботились, поэтому темой следующей главы будет обзор инструментов STL для организации обратных вызовов24.
4.6. Использование стандартной библиотеки
4.6.1. Организация вызовов
В стандартной библиотеке имеется полиморфный класс – оболочка std::function, предназначенная для организации вызовов различных типов. Этот класс идеально подходит на роль универсального аргумента. Кроме рассмотренных техник стирания типа и настройки сигнатуры, в нем реализовано множество других вещей: конструктор копирования, оператор присваивания, поддержка указателей на методы класса, проверка настройки аргумента, локальный буфер для хранения аргумента и многое другое. Мы не будем рассматривать реализацию std::function, потому что, во-первых, она достаточно сложная, а, во-вторых, может изменяться в зависимости от версии и платформы. При желании читатель сможет сделать это самостоятельно, проанализировав исходный код, мы же сосредоточимся на практическом использовании класса-оболочки.
Насколько сложна реализация std::function, настолько же просто ее использование. По аналогии с универсальным аргументом, рассмотренном в предыдущей главе, достаточно объявить экземпляр класса с нужной сигнатурой, после чего ему можно назначать различные объекты вызовов (Листинг 51).
void External(int eventID) {};
int main()
{
struct Call
{
void operator() (int eventID) {};
} objectCall;
std::function
fnt = External;
fnt = objectCall;
fnt = [](int evetID) {};
fnt(0);
}
Полезной особенностью std::function является проверка настройки объекта вызова. Если объект не настроен, т. е. не было ни одного присваивания, то при попытке вызова будет выброшено исключение. Проверить, настроен ли объект, можно с помощью перегруженного оператора bool, пример приведен в Листинг 52.
int main()
{
std::function
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).
class Initiator // (1)
{
public:
template
void setup(const CallbackArgument& argument) // (2)
{
callbackHandler = argument;
}
void run()
{
int eventID = 0;
//Some actions
callbackHandler(eventID);
}
private:
std::function
};