【问题标题】:C++ std::async() terminate on exception before calling future.get()C++ std::async() 在调用 future.get() 之前因异常终止
【发布时间】:2016-12-14 00:54:00
【问题描述】:

我正在尝试创建一个在捕获异常时调用 std::terminate() 的包装器。 我希望这个包装器采用与 std::async() 相同的参数(它可以是对函数的调用以及对方法的调用)。 有人知道如何编译这段代码吗?

谢谢

http://ideone.com/tL7mTv

#include <iostream>
#include <functional>
#include <future>

template<class Fn, class... Args>
inline auto runTerminateOnException(Fn&& fn, Args&&... args) {
    try {
        return std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...)();
    } catch (...) {
        std::terminate();
    }
}

template<class Fn, class... Args>
inline auto runAsyncTerminateOnException(Fn&& fn, Args&&... args) {
    return std::async(std::launch::async, runTerminateOnException<Fn, Args&&...>, std::forward<Fn>(fn), std::forward<Args>(args)...);
}

struct Foo {
    void print() {
        printf("Foo::print()\n");
    }
};

int main() {
    Foo foo;
    std::future<void> future = runAsyncTerminateOnException(&Foo::print, &foo);
    // your code goes here
    return 0;
}

【问题讨论】:

  • 它会编译
  • 它在你的电脑上编译?如果您点击 ideone.com 上的链接,则会出现编译错误
  • 不是这个问题的答案:但如果您想要runTerminateOnException 的等效行为,您可以将noexcept 说明符添加到传递给std::async 的函数中。这会在出现异常时调用std::terminate

标签: c++ c++11


【解决方案1】:

我个人认为你有点过于复杂了。您可以绑定调用并使用简单的 lambda 进行包装。

#include <iostream>
#include <future>
#include <functional>
#include <type_traits>

template<class Fn, class... Args>
inline auto runAsyncTerminateOnException(Fn&& fn, Args&&... args) {
    auto make_call = std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...);

    return std::async(std::launch::async, [=]() -> decltype(make_call()) {
        try {
            return make_call();
        } catch (...) {
            std::cout << "Terminate Called!" << std::endl;
            std::terminate();
        }
    });
}

struct Foo {
    void print() {
        printf("Foo::print()\n");
    }
    void print2() {
        printf("Foo::print2()\n");
        throw 1;
    }
};

int main() {
    Foo foo;
    std::future<void> future = runAsyncTerminateOnException(&Foo::print, &foo);
    std::future<void> future2 = runAsyncTerminateOnException(&Foo::print2, &foo);
    return 0;
}

See it live, with possible output.


我显然复制了第一个闭包,而不是执行将其移动到第二个闭包所需的步骤(就像在 c++11 中所做的那样)。您当然可以使用 c++14 中的特定移动捕获来移动它。

【讨论】:

  • 非常感谢。但是当调用的方法包含可变参数时,我遇到了一个问题。你可以在这里看到问题:ideone.com/psnQgJ。你知道为什么直接调用 std::async() 工作时它不起作用吗?
  • 是的,你可以看到它在这里与 async() 一起工作:ideone.com/2IiFvp
  • @user3782790 我必须说这让我很困惑。我需要调查
  • 我认为这是我一开始的第一个问题,因为我之前使用的是 lambda,但它不起作用,我通过在问题中不使用可变参数方法来过度简化我的代码。
  • @user3782790 问题不在于可变参数模板,而是Foo::print 中的转发引用 - 你最终得到一个void (Foo::*)(int&amp;&amp;) 函数,std::bind 似乎不喜欢...
【解决方案2】:

在 c++17 中,干净的方法是使用std::invoke

我已经在这里破解了它来演示。

#include <iostream>
#include <future>
#include <functional>
#include <type_traits>

namespace std
{
template<class T>
static constexpr bool is_member_pointer_v = std::is_member_pointer<T>::value;
template<class T>
static constexpr bool is_function_v = std::is_function<T>::value;
template<class B, class T>
static constexpr bool is_base_of_v = std::is_base_of<B, T>::value;
namespace detail {
template <class T>
struct is_reference_wrapper : std::false_type {};
template <class U>
struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {};
template <class T>
constexpr bool is_reference_wrapper_v = is_reference_wrapper<T>::value;

template <class Base, class T, class Derived, class... Args>
auto INVOKE(T Base::*pmf, Derived&& ref, Args&&... args)
    noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...)))
 -> std::enable_if_t<std::is_function_v<T> &&
                     std::is_base_of_v<Base, std::decay_t<Derived>>,
    decltype((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))>
{
      return (std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...);
}

template <class Base, class T, class RefWrap, class... Args>
auto INVOKE(T Base::*pmf, RefWrap&& ref, Args&&... args)
    noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...)))
 -> std::enable_if_t<std::is_function_v<T> &&
                     is_reference_wrapper_v<std::decay_t<RefWrap>>,
    decltype((ref.get().*pmf)(std::forward<Args>(args)...))>

{
      return (ref.get().*pmf)(std::forward<Args>(args)...);
}

template <class Base, class T, class Pointer, class... Args>
auto INVOKE(T Base::*pmf, Pointer&& ptr, Args&&... args)
    noexcept(noexcept(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...)))
 -> std::enable_if_t<std::is_function_v<T> &&
                     !is_reference_wrapper_v<std::decay_t<Pointer>> &&
                     !std::is_base_of_v<Base, std::decay_t<Pointer>>,
    decltype(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...))>
{
      return ((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...);
}

template <class Base, class T, class Derived>
auto INVOKE(T Base::*pmd, Derived&& ref)
    noexcept(noexcept(std::forward<Derived>(ref).*pmd))
 -> std::enable_if_t<!std::is_function_v<T> &&
                     std::is_base_of_v<Base, std::decay_t<Derived>>,
    decltype(std::forward<Derived>(ref).*pmd)>
{
      return std::forward<Derived>(ref).*pmd;
}

template <class Base, class T, class RefWrap>
auto INVOKE(T Base::*pmd, RefWrap&& ref)
    noexcept(noexcept(ref.get().*pmd))
 -> std::enable_if_t<!std::is_function_v<T> &&
                     is_reference_wrapper_v<std::decay_t<RefWrap>>,
    decltype(ref.get().*pmd)>
{
      return ref.get().*pmd;
}

template <class Base, class T, class Pointer>
auto INVOKE(T Base::*pmd, Pointer&& ptr)
    noexcept(noexcept((*std::forward<Pointer>(ptr)).*pmd))
 -> std::enable_if_t<!std::is_function_v<T> &&
                     !is_reference_wrapper_v<std::decay_t<Pointer>> &&
                     !std::is_base_of_v<Base, std::decay_t<Pointer>>,
    decltype((*std::forward<Pointer>(ptr)).*pmd)>
{
      return (*std::forward<Pointer>(ptr)).*pmd;
}

template <class F, class... Args>
auto INVOKE(F&& f, Args&&... args)
    noexcept(noexcept(std::forward<F>(f)(std::forward<Args>(args)...)))
 -> std::enable_if_t<!std::is_member_pointer_v<std::decay_t<F>>,
    decltype(std::forward<F>(f)(std::forward<Args>(args)...))>
{
      return std::forward<F>(f)(std::forward<Args>(args)...);
}
} // namespace detail

template< class F, class... ArgTypes >
auto invoke(F&& f, ArgTypes&&... args)
    // exception specification for QoI
    noexcept(noexcept(detail::INVOKE(std::forward<F>(f), std::forward<ArgTypes>(args)...)))
 -> decltype(detail::INVOKE(std::forward<F>(f), std::forward<ArgTypes>(args)...))
{
    return detail::INVOKE(std::forward<F>(f), std::forward<ArgTypes>(args)...);
}
}

template<class Fn, class... Args>
inline auto runAsyncTerminateOnException(Fn&& fn, Args&&... args) {

    return std::async(std::launch::async, [=]() -> decltype(auto) {
        try {
            return std::invoke(fn, args...);
        } catch (...) {
            std::cout << "Terminate Called!" << std::endl;
            std::terminate();
        }
    });
}

struct Foo {
    void print() {
        printf("Foo::print()\n");
    }
    void print2() {
        printf("Foo::print2()\n");
        throw 1;
    }
};

int main() {
    Foo foo;
    std::future<void> future = runAsyncTerminateOnException(&Foo::print, &foo);
    std::future<void> future2 = runAsyncTerminateOnException(&Foo::print2, &foo);
    return 0;
}

调用模板化成员函数时出错:

错误是这个&lt;source&gt;: In instantiation of 'runAsyncTerminateOnException(Fn&amp;&amp;, Args&amp;&amp; ...)::&lt;lambda()&gt; [with Fn = void (Foo::*)(int&amp;&amp;); Args = {Foo*, int}]':

暗示 Foo::print 要求int&amp;&amp; 当然是。那是你写的:

void print(Args&&... args)

打印函数要求对象的所有权是不合理的。声明它应该是:

struct Foo {
    template<class... Args>
    void print(const Args&... args) {
        printf("Foo::print(%d)\n", args...);
    }
};

【讨论】:

  • 感谢您的建议。但不幸的是,当调用的方法包含可变参数时,我有一个错误,而直接调用 std::async() 有效。这是调用可变参数方法的相同代码:ideone.com/N20Vi4
  • 当然我没有使用打印功能。这是一个简化的例子。我需要以标准方式转发参数,这就是 C++ 标准库处理可变参数模板的方式。您可以在所有 C++ 标准库函数中看到这种模式
  • 对 std::async() 的调用可以完美地处理打印功能,所以我希望它的包装器也能正常工作
  • 您可以将 noexcept 添加到 lambda,而不是捕获异常并调用终止。您可以模拟std::async 的 DECAY_COPY 转发行为,方法是将 args 捕获为 lambda 捕获,然后将它们转发到 invoke 调用(您的版本制作副本,因此不支持仅移动类型)。这很容易使用元组和std::apply,例如std::async(std::launch::async, [fn=std::forward&lt;Fn&gt;(fn), args=std::make_tuple(std::forward&lt;Args&gt;(args)...)]() mutable noexcept -&gt; decltype(auto) { return std::apply(std::move(fn), std::move(args)); });
  • 伟大的点@JonathanWakely 使用传递给 std::async 的 noexcept 甚至可能是比实现 runAsyncTerminateOnException 函数更好的解决方案
【解决方案3】:

我找到了 c++17 的解决方案。 它只有在我们不使用 auto 作为 runTerminateOnException() 的返回类型时才有效。

template<class Fn, class... Args>
inline std::result_of_t<Fn&&(Args&&...)> runTerminateOnException(Fn&& fn, Args&&... args) {
    try {
        return std::invoke(std::forward<Fn>(fn), std::forward<Args>(args)...);
    } catch (...) {
        std::terminate();
    }
}

template<class Fn, class... Args>
inline auto runAsyncTerminateOnException(Fn&& fn, Args&&... args) {
    return std::async(std::launch::async, runTerminateOnException<Fn, Args&&...>, std::forward<Fn>(fn), std::forward<Args>(args)...);
}

【讨论】:

  • 您可以通过在函数中添加noexcept 说明符来删除第一个函数中的try catch 块。事实上,在传递给 std::async 的函数上使用 noexcept 说明符是一种可行的替代方案,甚至首先需要实现 runTerminateOnException 函数。
猜你喜欢
  • 1970-01-01
  • 2019-11-29
  • 2019-06-22
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
  • 2014-08-29
  • 2014-02-19
  • 2011-11-14
相关资源
最近更新 更多