首页 > 解决方案 > 在存在异常的情况下使用 for_each?std::exception_list

问题描述

cppreference 文档https://en.cppreference.com/w/cpp/algorithm/for_each 说:

  • 如果作为算法的一部分调用的函数的执行抛出异常并且 ExecutionPolicy 是三个标准策略之一,则调用 std::terminate。对于任何其他 ExecutionPolicy,行为是实现定义的。

我解释这意味着我不能开箱即用地从for_each传递的函数中抛出并期望捕获异常或与之相关的一些信息。

我期望使用异常的原因是我可以部分撤消(恢复)for_each调用中所做的更改。(也许有更好的算法)。

然而,我偶然发现了一个历史版本,for_each它有一个不同的、更有趣的行为:

http://man.hubwiz.com/docset/C.docset/Contents/Resources/Documents/output/en/cpp/algorithm/for_each.html

  • 如果策略是 std::parallel_vector_execution_policy,则调用 std::terminate
  • 如果 policy 是 std::sequential_execution_policy 或 std::parallel_execution_policy,则算法以包含所有未捕获异常的 std::exception_list 退出。如果只有一个未捕获的异常,算法可能会重新抛出它,而不用包装在 std::exception_list 中。在遇到第一个异常后返回之前,算法将执行多少工作是未指定的。

这似乎意味着terminate实际上有可能使用异常而不是 ing。

那么,为什么std::exception_list会被淘汰呢?是不是太有争议、太复杂、太(内存)成本了?

即使我同意这个逻辑,我真的没有任何其他选择,因为并行for_each返回void(而不是 UnaryFunction 返回,这也令人惊讶)。因此,在我看来,该协议是撤消未完成指令std::exception_list的必要组成部分。for_each

期望一些新的自定义策略是否合理,例如par_with_failed_list将出现在允许undoing 的某个地方。


更多上下文:这种撤消失败循环的模式用于容器的构造。我想实现一个自定义(并行/顺序)uninitialized_value_construct_n,当(任何未排序的)构造失败时“撤消”(销毁)初始化的对象。


EDIT1:不过,也许可以将 lambda 中的捕获变量传递给函数参数。此变量可以是共享并发数据,可以在异常发生时存储异常(作为 exception_list)。我想知道这是否已经完成。


EDIT2exception_list :我在 HPX 中找到了一个实现,
https ://github.com/STEllAR-GROUP/hpx/blob/master/hpx/exception_list.hpp
https://github.com/STEllAR-GROUP/hpx/blob/master /src/exception_list.cpp

标签: c++exceptionforeachc++17stl-algorithm

解决方案


std::exception_list给并行算法的规范和实现增加了很多复杂性,而没有多少相应的收益。

作为用户,您可以在仿函数中处理这种情况:

struct exception_info{
    ElementType* element;
    std::exception_ptr exception;
};
std::vector<exception_info> exceptions;
std::mutex exceptions_mutex;

std::vector<ElementType> range=...;

std::for_each(std::execution::par,range.begin(),range.end(),[&](ElementType& element){
    try{ do_stuff(element); }
    catch(...){
        std::lock_guard guard(exceptions_mutex);
        exceptions.push_back({&element,std::current_exception()});
    }});

exceptions列表现在将包含指向引发异常的元素和引发的异常的指针列表。


推荐阅读