【问题标题】:Usage of for_each in the presence of exceptions? std::exception_list在存在异常的情况下使用 for_each? std::exception_list
【发布时间】:2019-03-13 07:12:09
【问题描述】:

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

这似乎暗示着实际上可能使用异常而不是terminateing。

那么,为什么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)。 我想知道这是否已经完成。


EDIT2:我在 HPX 中找到了 exception_list 的实现,
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++ exception foreach c++17 stl-algorithm


【解决方案1】:

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 列表现在将包含一个指针列表,指向引发异常的元素和引发的异常。

【讨论】:

  • 太棒了,我试图模拟exception_list,但这更直接。我猜想是使用链表还是数组来存储异常的简单决定足以打开一罐蠕虫。也许这些决策是执行策略的一部分 (for_each(std::execution::par_guarded(exceptions_vector/list))。另外,在我的上下文中,这种类型的异常的有趣之处在于它们必须有足够的信息来撤消引发它的操作。
  • 我现在很好奇:如果我理解正确,current_exception 会返回一个类似于当前异常的非拥有指针。当我可以访问exceptions-vector 时(在for_each 之后),所有不同的异常怎么可能仍然存在(没有被破坏)?代码中的exceptions 不只是末尾的悬空指针数组吗?
  • std::current_exception() 返回一个std::exception_ptr,它是一个拥有指针。指针要么保持原始异常处于活动状态,直到指针被销毁,要么保持足够的信息以在被询问时重新抛出异常的副本。
  • 啊,错过了文档中的“拥有部分”。我是否正确,它就像一个 share_ptr (具有自己的计数和锁定?)并且唯一可以用它做的操作是重新抛出(副本?)?
  • 是的,它很可能像 shared_ptr 一样被引用计数。除了复制指针之外,您唯一能做的就是调用rethrow_exception,这可能会抛出原件或副本。
猜你喜欢
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 2020-10-21
  • 2020-03-08
  • 2010-12-15
  • 1970-01-01
相关资源
最近更新 更多