在现代 C++ 中使用 try..catch 块通过模板元编程包装任意函数调用
Wrap arbitrary function call via template metaprogramming with try..catch block in modern C++
我想创建一些模板,基本上应该包装它的参数。参数应该是一个任意的函数调用,它通过一些带有前缀和后缀代码的模板元编程魔法被包装。
我想像下面这样使用它:
auto result = try_call( some_vector.at(13) );
和 try_call
将以某种方式定义,它在 some_vector.at(13)
周围包装了一个 try..catch 块。像这样:
template<typename T>
// some template metaprogramming magic here
try {
auto value = // execute the parameter here, i.e. some_vector.at(13);
return std::experimental::optional<T>(value);
}
catch (std::exception&) {
return std::experimental::nullopt;
}
有 Bjarne Stroustrup 的那篇论文,但这并没有完全描述我需要的东西,而且我找不到解决这个问题的方法。
如果这不能直接实现,我目前正在考虑通过采用 lambda 的模板函数来实现:
template<typename Func>
auto try_call(Func f) {
try {
return f();
} catch(std::exception&) {
return std::experimental::nullopt;
}
}
但我不知道这是否是个好主意。我猜 lambda 有一些开销?我想避免任何不必要的开销。
实际上,您使用 lambda 的解决方案非常好且高效。从类型理论的角度来看,try_call
是一个高阶函数:它将另一个函数作为参数并在 try catch
上下文中执行它。
template<typename Func>
auto try_call(Func f) -> std::experimental::optional<std::decay_t<decltype(f())>> {
try {
return std::experimental::make_optional(f());
} catch(std::exception&) {
return std::experimental::nullopt;
}
}
用 lambda 调用它会产生你想要的结果而没有任何开销。 lambda 被编译为具有重载函数调用运算符的匿名结构。此结构用作 try_call
函数的模板参数。因此,编译器在调用 f()
时确切地知道要执行的函数,并将其内联。不涉及开销。
我玩过这个;而且,这就是我想出的一个可能的解决方案。
// Function
template<typename ReturnType, typename... Args, typename... UArgs>
ReturnType no_throw_call(ReturnType (*f)(Args...), UArgs... args)
{
try { return f(args...); }
catch (...) {}
return ReturnType();
}
// Method
template<typename ReturnType, typename Class, typename... Args, typename... UArgs>
ReturnType no_throw_call_method(Class &c, ReturnType(Class::*m)(Args...), UArgs... args)
{
try { return (c.*m)(args...); }
catch (...) {}
return ReturnType();
}
注意:当类型之间存在允许的转换时,如果类型与函数的签名不完全匹配,则使用 UArgs 允许类型的自动转换。
此外,此代码保证返回默认的初始化值。
我建议,如果使用这种类型的代码,您应该在 catch 块中包含某种错误日志记录。这可能会向您的客户隐藏您的错误,但您不想向您的开发人员和 QA 隐藏您的错误。
事实上,我建议使用此调试版本,以实际允许您的程序崩溃,就像在 QA 测试期间应该发生的那样。否则,您的客户可能会遇到一些不良的未定义行为,因为您隐藏了错误而未能找到它们。
用法示例:
#define nt_strcpy(X,Y) no_throw_call(strcpy, (X), (Y))
nt_strcpy(nullptr, nullptr);
B b;
// this calls a method named do_it which takes an int as a parameter and returns void
no_throw_call_method<void>(b, &B::do_it, 1);
我想创建一些模板,基本上应该包装它的参数。参数应该是一个任意的函数调用,它通过一些带有前缀和后缀代码的模板元编程魔法被包装。
我想像下面这样使用它:
auto result = try_call( some_vector.at(13) );
和 try_call
将以某种方式定义,它在 some_vector.at(13)
周围包装了一个 try..catch 块。像这样:
template<typename T>
// some template metaprogramming magic here
try {
auto value = // execute the parameter here, i.e. some_vector.at(13);
return std::experimental::optional<T>(value);
}
catch (std::exception&) {
return std::experimental::nullopt;
}
有 Bjarne Stroustrup 的那篇论文,但这并没有完全描述我需要的东西,而且我找不到解决这个问题的方法。
如果这不能直接实现,我目前正在考虑通过采用 lambda 的模板函数来实现:
template<typename Func>
auto try_call(Func f) {
try {
return f();
} catch(std::exception&) {
return std::experimental::nullopt;
}
}
但我不知道这是否是个好主意。我猜 lambda 有一些开销?我想避免任何不必要的开销。
实际上,您使用 lambda 的解决方案非常好且高效。从类型理论的角度来看,try_call
是一个高阶函数:它将另一个函数作为参数并在 try catch
上下文中执行它。
template<typename Func>
auto try_call(Func f) -> std::experimental::optional<std::decay_t<decltype(f())>> {
try {
return std::experimental::make_optional(f());
} catch(std::exception&) {
return std::experimental::nullopt;
}
}
用 lambda 调用它会产生你想要的结果而没有任何开销。 lambda 被编译为具有重载函数调用运算符的匿名结构。此结构用作 try_call
函数的模板参数。因此,编译器在调用 f()
时确切地知道要执行的函数,并将其内联。不涉及开销。
我玩过这个;而且,这就是我想出的一个可能的解决方案。
// Function
template<typename ReturnType, typename... Args, typename... UArgs>
ReturnType no_throw_call(ReturnType (*f)(Args...), UArgs... args)
{
try { return f(args...); }
catch (...) {}
return ReturnType();
}
// Method
template<typename ReturnType, typename Class, typename... Args, typename... UArgs>
ReturnType no_throw_call_method(Class &c, ReturnType(Class::*m)(Args...), UArgs... args)
{
try { return (c.*m)(args...); }
catch (...) {}
return ReturnType();
}
注意:当类型之间存在允许的转换时,如果类型与函数的签名不完全匹配,则使用 UArgs 允许类型的自动转换。
此外,此代码保证返回默认的初始化值。
我建议,如果使用这种类型的代码,您应该在 catch 块中包含某种错误日志记录。这可能会向您的客户隐藏您的错误,但您不想向您的开发人员和 QA 隐藏您的错误。
事实上,我建议使用此调试版本,以实际允许您的程序崩溃,就像在 QA 测试期间应该发生的那样。否则,您的客户可能会遇到一些不良的未定义行为,因为您隐藏了错误而未能找到它们。
用法示例:
#define nt_strcpy(X,Y) no_throw_call(strcpy, (X), (Y))
nt_strcpy(nullptr, nullptr);
B b;
// this calls a method named do_it which takes an int as a parameter and returns void
no_throw_call_method<void>(b, &B::do_it, 1);