Обёртка функции C ++ для перехвата определенного сигнала - PullRequest
0 голосов
/ 19 октября 2018

Я пытаюсь использовать универсальную функцию «поймать определенный сигнал», которая будет обрабатывать сигнал определенным образом.

Функции имеют разные вкусы;некоторые без возврата (void) некоторые с возвратами (bool) и некоторые с параметрами (на самом деле только на).

Я получил его для компиляции с тремя вариантами одной и той же функции:

int64_t run_inside_abort_check_v( std::function<void(Device*)> fun_ptr );
template<typename Arg>
int64_t run_inside_abort_check_p( std::function<void(Device*, Arg )> fun_ptr, Arg arg );
int64_t run_inside_abort_check_r( bool* ret_value, std::function<bool (Device*)> fun_ptr );

Но для этого потребуются несколько отличающиеся три реализации - это кажется глупым.

Как я могу объединить эти три в одну функцию?

Например, примерная версия для одной версии arg:

template<typename Arg>
int64_t Class::run_inside_abort_check( std::function<void(Device*, Arg)> fun_ptr, Arg args )
{
    try
    {
      if ( flag_abort )
         throw SignalAborted();

      fun_ptr( this->device,  arg ); // Call the wanted function that might thrown SignalToCatch
    }
    catch ( const SignalToCatch& sig )
    {
       device->handle_trouble_case();
       return (int64_t)ERROR_TROUBLE;
    }
    return 0x00;
}

Как @VTT указал наслучаи (1) и (2) похожи, другие с пустыми аргументами: Когда я пытаюсь сделать так, компиляция завершается неудачно:

    #include <iostream>
    #include <functional>

    class Device
    {
    public:
        void foo1() { std::cout << "foo called\n"; };
        void foo2( int bar ) { std::cout << "foo2 called:" << bar << "\n"; };
    };

    template<typename Arg>
    int64_t run_inside_abort_check_p( std::function<void(Device*, Arg )> fun_ptr, Arg arg );

    template<typename ... Arg>
    int64_t run_inside_abort_check_va( std::function<void(Device*, Arg...  )> fun_ptr, Arg ... arg );

    int main()
    {
        int foo;
        run_inside_abort_check_p<int>( &Device::foo2, foo ); // works fine!
        run_inside_abort_check_va<int>( &Device::foo2, foo );
    }

Производит:

 error: no matching function for call to ‘run_inside_abort_check_va<int>(void (Device::*)(int), int&)’

 silly.cpp:18:9: note:   template argument deduction/substitution failed:
 silly.cpp:23:56: note:   mismatched types ‘std::function<void(Device*, Arg ...)>’ and ‘void (Device::*)(int)’
 run_inside_abort_check_va<int>( &Device::foo2, foo );

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Мне лично не нравится возврат значения через возможный параметр нулевого указателя.Поэтому я возвращаюсь через класс, который инкапсулирует код ошибки и необязательное возвращаемое значение функции.Большое осложнение связано с тем, что 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};

    }
}
0 голосов
/ 19 октября 2018

Ответ, который, как я вижу, завершается, решает проблему компиляции, как описано здесь: Ошибка замены шаблона с помощью std :: function

Объект функции должен быть точно указан следующим образом:

  run_inside_abort_check_va( std::function<void(Device*,int)>{ &Device::foo2}, foo );
  run_inside_abort_check_va( std::function<void(Device*)>{ &Device::foo1} );

Тогда все работает нормально (при условии, что для случая (3) значение параметра передается как параметр).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...