在现代C ++中使用try..catch块通过模板元编程包装任意函数调用

时间:2015-07-20 21:37:42

标签: c++ templates c++11 lambda c++14

我想创建一些基本上应该包装它的参数的模板。该参数应该是一个任意函数调用,它通过一些模板元编程魔法包含前缀和后缀代码。

我想使用它如下:

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有一些开销吗?我想避免任何不必要的开销。

2 个答案:

答案 0 :(得分:5)

实际上,使用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()时要执行的函数,并且它将被内联。不涉及任何开销。

答案 1 :(得分:0)

我玩弄了这个;而且,这就是我想出的可能的解决方案。

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