Сложный «калькулятор» в настоящее время вызывается следующим образом:
result = calculator.calc(a, b, extra);
К сожалению, иногда это может привести к сбою из-за несвязанных изменений, происходящих на сервере. Синхронизироваться с разными отделами сложно — повторить попытку проще…
С этой целью я реализовал retry
лямбда-функцию:
auto retry = [](const auto& func, auto... args) {
int attempts = 5;
for (;;) try {
return func(args...);
}
catch (const std::system_error& e) {
/* Don't retry in case of a system_error */
throw;
}
catch (const exception& e) {
if (attempts-- == 0)
throw; /* Give up and rethrow */
Logging::log(Logging::Level::Warning,
"%s. Retrying %d more times.", e.what(), attempts);
sleep(2);
}
};
Вышеупомянутое компилируется, но, когда я пытаюсь его использовать:
result = retry(calculator.calc, a, b, extra);
Я получаю сообщение об ошибке от clang: reference to non-static member function must be called
. GNU C++ помечает одну и ту же строку знаком invalid use of non-static member function
.
Действительно, calc()
— это нестатический метод класса Calculator.
Как бы я использовал свою новую лямбду?
Потому что вызывающая функция сама по себе является шаблоном, и создавать новый мне не хотелось :)
В лямбде замените func(args...)
на std::invoke(func, args...)
. Теперь ваша лямбда поддерживает указатели на функции-члены как вызываемые в дополнение к указателям/ссылкам на функции, лямбда-выражениям и другим объектам функций.
Чтобы передать указатель на функцию-член, синтаксис следующий:
result = retry(&Calculator::calc, &calculator, a, b, extra);
где Calculator
— тип класса calculator
. Дополнительный аргумент &calculator
необходим для вызова нестатической функции-члена указанного объекта/экземпляра класса. std::invoke
умеет справляться с этим автоматически.
Здесь нужно быть немного осторожным. Поскольку вы используете аргументы по значению, если вы напишете calculator
вместо &calculator
, вы случайно вызовете функцию-член для копии calculator
.
Обычно было бы лучше передавать аргументы путем пересылки ссылки:
auto retry = [](auto&& func, auto&&... args) {
Тогда вы также можете назвать это как
result = retry(&Calculator::calc, calculator, a, b, extra);
std::invoke
знает, как обрабатывать указатели и ссылки на экземпляр класса при вызове указателей на функции-члены.
Однако обратите внимание: поскольку вы потенциально вызываете func
несколько раз, вам не следует std::forward
использовать ссылки на пересылку в вызове.
Кроме того, если вы хотите сохранить retry
общим, недостаточно поймать std::exception
. Нет требования, чтобы исключения производились от std::exception
. Это всего лишь соглашение, используемое в стандартной библиотеке, а иногда и в других библиотеках.
Резервный блок catch
для всех остальных исключений должен выглядеть так:
catch(...)
{
// do something
}
Альтернативой является передача calculator
в списке захвата лямбды, а не в качестве аргумента.
Вы можете передать напрямую лямбду:
retry([&](){ return calculator.calc(a, b, extra); });
Почему это лямбда, а не обычная именованная функция?