Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
}
};
template > // (3)
void sort_bubble(Data* data, size_t size, Predicate less = Predicate()) // (4)
{
for (size_t i = 0; i < size – 1; i++)
{
for (size_t j = 0; j < size – i – 1; j++)
{
if (less (data[j + 1], data[j]))
{
Data temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
В строке 1 объявлен шаблон для структуры, реализующей предикат сравнения. В этой структуре перегружен оператор (строка 2), который возвращает результат сравнения двух аргументов. Он будет корректно работать как для чисел, так и для объектов, в которых перегружен оператор «меньше».
В строке 3 объявлен шаблон для функции сортировки. Первый параметр шаблона – это тип данных, которые необходимо сортировать, а второй параметр – это тип предиката. По умолчанию типом предиката является структура, объявленная выше, которая инстанциируется соответствующим типом данных.
В строке 4 объявлена функция шаблона. Первый параметр здесь – это данные для сортировки, а второй параметр – предикат для вычисления меньшего элемента. Если при вызове функции предикат не задан, то в качестве значения по умолчанию будет подставлена переменная – экземпляр структуры, объявленной в строке 1. Инстанциироваться эта структура будет типом Data, переданным как первый параметр шаблона.
Итак, на примере алгоритма сортировки мы рассмотрели, как реализуются предикаты для выбора меньшего элемента из двух. Подобным образом можно реализовать множество других операций: сравнения, сложения, вычисления хэш-суммы и т. п. Таким образом, предикаты предлагают удобный способ реализации арифметико-логических операций с нечисловыми типами данных. Частично снимается проблема монолитной архитектуры при использовании функциональных объектов: мы можем реализовать любое количество нужных объектов и подставлять их в шаблон по мере необходимости 19 19 Мы употребили термин «частично», потому что полной независимости здесь нет: при изменении функционального объекта нужно перекомпилировать как инициатор, так и исполнитель. Таким образом, независимость здесь обеспечивается только на уровне исходного кода.
. И в заключение отметим, что концепция предикатов широко используется в реализации алгоритмов стандартной библиотеки STL.
4.4. Асинхронные вызовы
4.4.1. Инициатор
Также, как мы делали при анализе синхронных вызовов, проанализируем различные реализации инициатора асинхронных вызовов (Листинг 36, некоторые фрагменты кода пропущены, чтобы не загромождать описание).
class Executor;
class CallbackHandler
{
public:
void operator() (int eventID);
};
//Pointer to function
class Initiator1
{
public:
using ptr_callback = void(*) (int, void*);
void setup(ptr_callback pPtrCallback, void* pContextData) ;
private:
ptr_callback ptrCallback = nullptr;
void* contextData = nullptr;
};
//Pointer to the class static method
class Initiator2
{
public:
using ptr_callback_static = void(*) (int, Executor*);
void setup(ptr_callback_static pPtrCallback, Executor* pContextData) ;
private:
ptr_callback_static ptrCallback = nullptr;
Executor* contextData = nullptr;
};
//Pointer to the class member method
class Initiator3
{
public:
using ptr_callback_method = void(Executor::*)(int);
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
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()
Интервал:
Закладка: