Мне лично не нравится возврат значения через возможный параметр нулевого указателя.Поэтому я возвращаюсь через класс, который инкапсулирует код ошибки и необязательное возвращаемое значение функции.Большое осложнение связано с тем, что void
является неполным типом, и вы не можете сохранить возврат функции, возвращающей void.Поэтому я специализируюсь на этом классе для возврата типа void:
template <class T> struct Check_result
{
std::int64_t error_code;
std::optional<T> return_value;
};
template <> struct Check_result<void>
{
std::int64_t error_code;
};
Я стараюсь избегать использования std::function
, когда функцию не нужно сохранять и поэтому для функции проверки я использую параметр шаблона.Помните осложнение с void
, о котором я говорил?Здесь C ++ 17 пригодится с if constexpr
.
template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> Check_result<R>
{
try
{
if constexpr (std::is_void_v<R>)
{
f(device);
return {std::int64_t{0}};
}
else
{
return {std::int64_t{0}, f(device)};
}
}
catch (const SignalToCatch& sig)
{
handle_trouble_case();
if constexpr (std::is_void_v<R>)
return {std::int64_t{11}};
else
return {std::int64_t{11}, {}};
}
}
Альтернативой для упрощения логики и потока управления внутри maybe_void(f, device)
является создание полного эквивалента типа void
и помощникафункция maybe_void
, которая преобразует void
в наш полный тип:
struct Complete_void{};
template <class F, class... Args> auto maybe_void(F&& f, Args&&... args)
{
using R = decltype(std::forward<F>(f)(std::forward<Args>(args)...));
if constexpr (std::is_void_v<R>)
{
std::forward<F>(f)(std::forward<Args>(args)...);
return Complete_void{};
}
else
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
}
Затем мы модифицируем Check_result
для обработки Complete_void
:
template <> struct Check_result<void>
{
std::int64_t error_code;
std::optional<Complete_void> return_value;
};
И теперь мы можем упростить нашфункция значительно:
template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> Check_result<R>
{
try
{
return {std::int64_t{0}, maybe_void(f, device)};
}
catch (const SignalToCatch& sig)
{
handle_trouble_case();
return {std::int64_t{11}, {}};
}
}
Использование:
class Device
{
public:
void foo1() {};
void foo2(int) {};
int foo3(int, int);
};
int main()
{
X x;
x.device = Device{};
Check_result r1 = x.run_inside_abort_check([](Device& d) { return d.foo1();});
r1.error_code;
Check_result r2 = x.run_inside_abort_check([](Device& d) { return d.foo2(24);}) ;
r2.error_code;
Check_result r3 = x.run_inside_abort_check([](Device& d) { return d.foo3(24, 13);});
r3.error_code;
if (r3.return_value)
{
int r = *r3.return_value;
}
}
C ++ 14 решение
Если вам не нужно обрабатывать как возвращаемые пустые, так и не пустые случаитогда вышеприведенное можно легко адаптировать к C ++ 14.
Если вам нужно обработать оба случая, это также можно сделать в C ++ 14:
Для std::optional
используйте boost::optional
или, если это невозможно, используйте std::unique_ptr
.
Что касается constexpr if, то здесь мы используем SFINAE и дублируем для двух случаев.Я не вижу выхода из этого:
template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> std::enable_if_t<std::is_void_v<R>, Check_result<R>>
{
try
{
f(device);
return Check_result<R>{std::int64_t{0}};
}
catch (const SignalToCatch& sig)
{
handle_trouble_case();
return Check_result<R>{std::int64_t{11}};
}
}
template<class F, class R = decltype(std::declval<F>()(device))>
auto run_inside_abort_check(F f) -> std::enable_if_t<!std::is_void_v<R>, Check_result<R>>
{
try
{
return Check_result<R>{std::int64_t{0}, std::make_unique<R>(f(device))};
}
catch (const SignalToCatch& sig)
{
handle_trouble_case();
return Check_result<R>{std::int64_t{11}, nullptr};
}
}