Виталий Ткаченко - Обратные вызовы в C++
- Название:Обратные вызовы в C++
- Автор:
- Жанр:
- Издательство:Array SelfPub.ru
- Год:2021
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Виталий Ткаченко - Обратные вызовы в C++ краткое содержание
Обратные вызовы в C++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
FO fo;
int eventID = 0;
auto lambda = [](int eventID) {};
auto callbackToMethod = std::bind(&FO::callbackHandler, fo, std::placeholders::_1);
StaticDistributorVoid distributor(ExternalHandler, fo, callbackToMethod, lambda); // (1)
distributor(eventID); // (2)
}
Как видим, использование очень простое: в строке 1 объявляется распределитель, в конструктор передаются объекты вызова, через перегруженный оператор 2 производятся вызовы сохраненных объектов.
5.5.2. Распределение с возвратом результатов
Если нужно получить значения, возвращаемые вызовами, то в распределителе необходимо модифицировать перегруженный оператор (Листинг 77).
template // (1)
class StaticDistributorReturn
{
public:
StaticDistributorReturn(CallObjects… objects) : callObjects(objects…) {} // (2)
auto& tuple() { return callObjects; } // (3)
template // (4)
auto operator() (CallData… callData)
{
return DistributeReturn(callObjects, callData…);
}
private:
std::tuple callObjects; // (5)
};
В строке 4 объявлен перегруженный оператор с возвращаемым типом auto. Указанный тип будет выведен из значения, возвращаемого соответствующей распределяющей функцией. (реализацию см. в Листинг 73 п. 5.4.1).
Пример использования распределителя приведен в Листинг 78.
struct FO
{
int operator() (int eventID) { return 10; }
int callbackHandler(int eventID) { return 0; }
};
struct SResult
{
unsigned int code;
const char* description;
};
SResult ExternalHandler(int eventID)
{
return SResult{ 1, "this is an error" };
}
int main()
{
FO fo;
int eventID = 0;
auto lambda = [](int eventID) { return 0.0; };
auto callbackToMethod = std::bind(&FO::callbackHandler, fo, std::placeholders::_1);
StaticDistributorReturn distributor(ExternalHandler, fo, callbackToMethod, lambda); // (1)
auto [resExtHandler, resFoOperator, resFoMethod, resLambda] = distributor(eventID); // (2)
}
В строке 1 объявляется распределитель, в конструктор передаются объекты вызова. Через перегруженный оператор 2 производятся вызовы хранимых объектов, результаты возвращаются с помощью структурных привязок.
К сожалению, мы не можем использовать рассмотренную реализацию для объектов, которые не возвращают результатов. Это связано с тем, что результаты выполнения вызовов возвращаются через кортеж, а он не может хранить типы void. Для таких вызовов нужно использовать реализацию, рассмотренную в предыдущем параграфе.
5.5.3. Параметризация возвращаемого значения
Итак, у нас имеется отдельная реализация распределителя для случая, когда результаты вызовов не требуются, и отдельная реализация для случая, когда необходимо получать возвращаемые значения. Обе реализации одинаковы, за исключением перегруженного оператора. Как сделать общую реализацию для обеих случаев? Разместить два перегруженных оператора в одном классе не получится, потому что они различаются только типом возвращаемого значения. Можно предложить следующее решение: ввести в шаблон дополнительный параметр, который указывает, нужно ли возвращать результаты выполнения вызовов, и в зависимости от этого по-разному формировать перегруженный оператор с помощью условной компиляции. Реализация приведена в Листинг 79.
template // (1)
class StaticDistributor
{
public:
StaticDistributor(CallObjects… objects) : callObjects(objects…) {} // (2)
auto& tuple() { return callObjects; } // (3)
template
auto operator() (CallData… callData) // (4)
{
#define callObject std::get<0>(callObjects) // (5)
#define callObjType decltype(callObject) // (6)
#define callObjInstance std::declval() // (7)
#define testCall callObjInstance(callData…) // (8)
#define retType decltype(testCall) // (9)
//if constexpr (std::is_same_v<0>(callObjects))>()(callData…))>) // (10)
if constexpr (std::is_same_v) // (11)
return Distribute2(callObjects, callData…); // (12)
else
return DistributeReturn(callObjects, callData…); // (13)
}
private:
std::tuple callObjects;
};
В строках 1 – 4 код идентичен реализации распределителя в предыдущих случаях (Листинг 75 п. 5.5.1, Листинг 77 п. 5.5.2). Интерес представляет реализация перегруженного оператора (строка 4).
Макросы в строках 5 – 9 предназначены только для облегчения понимания кода, без них конструкция получается запутанной (строка 10).
В строке 5 мы получаем объект вызова, для которого будет проверяться, возвращает ли он значение. Мы запрашиваем нулевой элемент кортежа, поскольку предполагается, что кортеж содержит хотя-бы один объект (иначе зачем распределять вызовы для пустого кортежа?).
В строке 6 определяется тип объекта, который мы запросили. В строке 7 объявляется мета-экземпляр объекта соответствующего типа. Мы говорим «мета-экземпляр», потому что реально объект не создается, но его характеристики используются компилятором для анализа. Конструкция declvalнеобходима, чтобы не было ошибки в случае, если объект не имеет конструктора по умолчанию.
В строке 8 производится мета-вызов с передачей параметров. Мета-вызов здесь имеет тот же смысл, что и мета-экземпляр, т. е. в реальности вызов не производится, а используется для анализа. В строке 9 определяется тип значения, возвращаемого мета-вызовом.
В строке 11 проверяется, является ли тип возвращаемого значения void, и в этом случае вызывается распределяющая функция без возврата результатов (строка 12). В противном случае вызывается распределяющая функция, возвращающая результаты (строка 13).
Использование распределителя с условной компиляцией приведено в Листинг 80.
struct FOReturn
{
int operator() (int eventID) {return 10;}
};
struct FOVoid
{
void operator() (int eventID) { /*do something*/ }
};
struct SResult
{
unsigned int code;
const char* description;
};
SResult ExternalReturn(int eventID)
{
return SResult{ 1, "this is an error" };
}
void ExternalVoid(int eventID)
{
}
int main()
{
int eventID = 0;
FOReturn foRet;
FOVoid foVoid;
auto lambdaRet = [](int eventID) { return 0.0; };
auto lambdaVoid = [](int eventID) {};
using FunPtrRet = SResult(*)(int);
using LambdaTypeRet = decltype(lambdaRet);
using FunPtrVoid = void(*)(int);
using LambdaTypeVoid = decltype(lambdaVoid);
StaticDistributor distributor1(foRet, ExternalReturn, lambdaRet); // (1)
StaticDistributor distributor2(foVoid, ExternalVoid, lambdaVoid); // (2)
auto results = distributor1(eventID);
distributor2(eventID);
}
Как видим, в обоих случаях объявляется один и тот же распределитель, а из свойств объектов распределения будет генерироваться соответствующий перегруженный оператор.
Читать дальшеИнтервал:
Закладка: