C++ 函数包装器捕获特定信号

标签 c++ templates c++14 variadic-templates

我正在尝试具有通用的“捕获特定信号”功能,该功能将以特定方式处理信号。

功能有不同的风格;有些没有返回值(void)有些有返回值(bool)有些有参数(实际上只有on)。

我用同一函数的三个变体编译它:

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 );

最佳答案

我个人不喜欢通过可能的空指针参数返回值。所以我通过一个封装错误代码和可选函数返回值的类返回。 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解决方案

如果您不需要同时处理返回 void 和非 void 的情况,那么上面的内容可以简单地适应 C++14。

如果您需要处理这两种情况,也可以在 C++14 中完成:

对于 std::optional 使用 boost::optional 或者,如果它不可用,使用 std::unique_ptr

至于 if constexpr,我们在这里使用 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};

    }
}

关于C++ 函数包装器捕获特定信号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52892084/

相关文章:

Magento - 创建新的标题链接 block

c++ - 数据不匹配和编译器无法推断模板参数

c++ - 在扣除自动之前不能使用捕获的变量

c++ - 如何有效地将底层数据从 std::string 移动到另一种类型的变量?

c++ - 优化switch(x),x是作为参数传递的常量

c++ - clang 使用了错误的系统包含目录

c++ - 读取以二进制格式存储在字符数组中的 double

c++ - Windbg 条件断点未按预期工作,我的语法是否正确?

c# - 在实例类中包装静态非托管库

c++ - 函数模板重载和歧义