我正在尝试具有通用的“捕获特定信号”功能,该功能将以特定方式处理信号。
功能有不同的风格;有些没有返回值(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/