【问题标题】:How does std::async "store" an arbitrary exception?std::async 如何“存储”任意异常?
【发布时间】:2015-02-23 22:52:27
【问题描述】:

我无法理解std::async 怎么可能存储任何异常,而不仅仅是源自std::exception 的东西。我玩弄了下面的代码

#include <iostream>
#include <future>
#include <chrono>

void f()
{
    std::cout << "\t\tIn f() we throw an exception" << std::endl;
    throw 1; // throw an int
}

int main()
{
    std::future<void> fut = std::async(std::launch::async, f);
    std::cout << "Main thread sleeping 1s..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1)); // sleep one second
    std::cout << "Main thread waking up" << std::endl;
    try
    {
        fut.get();
    }
    catch(...)
    {
        std::cout << "We caught an exception!" << std::endl;
        throw; // rethrow
    }
}

我异步启动f(),然后在f 中抛出一个int。神奇的是,intstd::async 返回的未来捕获并存储。我知道catch(...)std::async 中的异常是可能的,但是后者如何在不知道异常类型的情况下存储它呢?异常不是从某个基类派生的(在这种情况下,也许可以通过一些Base::clone“克隆”它),但可以是任何异常。我们能不能神奇地“推断”出异常类型?

总结一下,我的问题是:

我们如何在对象中存储任意异常,然后在不知道异常类型的情况下稍后重新抛出它?

【问题讨论】:

  • @BryanChen 谢谢,不知道这个,但我想问题仍然存在,exception_ptr 如何通过std::current_exception() 推断类型?似乎std::current_exception 负责“捕获”异常。
  • 它不只是将其存储为std::type_info吗?
  • @Xiao 在运行时抛出异常,这本质上是我的问题,类型是如何“捕获”的?看起来在内部发生了类似于模板/自动类型推导的事情。
  • 额外信息:std::exception_ptr 无法在可移植 C++ 中实现。它必须在同时实现异常抛出和捕获的“运行时库”中实现,并且编译器将创建对该库的调用(因此编译器和运行时库必须就私有 API 达成一致)。

标签: c++ exception c++11 asynchronous future


【解决方案1】:

std::async 可以在std::threadstd::packaged_task 之上实现。

std::packaged_task 可以(部分)在std::exception_ptr 和相关函数(线程退出就绪函数除外)之上实现。

std::exception_ptr及相关函数不能用C++编写。

【讨论】:

  • 谢谢,我现在意识到库的某些部分不能用语言本身编写;)
  • 它们可以用 C++ 编写(并且是),只是不可移植。
【解决方案2】:

我不确定这是否能准确回答您的问题,但这个示例可能会有所帮助。

我编译了以下内容:

int main()
{
    throw 1;
}

用命令

g++ -fdump-tree-gimple -std=c++11 main.cpp -o main

gimple(gcc 的中间输出)是:

int main() ()
{
  void * D.1970;
  int D.1974;

  D.1970 = __cxa_allocate_exception (4);
  try
    {
      MEM[(int *)D.1970] = 1;
    }
  catch
    {
      __cxa_free_exception (D.1970);
    }
  __cxa_throw (D.1970, &_ZTIi, 0B);
  D.1974 = 0;
  return D.1974;
}

所以它使用代表类型的符号地址调用__cxa_throw。在本例中,类型为_ZTIi,即整数的mangled 类型。

编译时不可用的类型

类型符号只需要在运行时可用。在试图隐藏尽可能多的符号的动态库中,它需要确保任何未在内部捕获和处理的异常都是可用的。有关详细信息,请参阅 https://gcc.gnu.org/wiki/Visibility,尤其是 Problems with C++ exceptions (please read!) 部分。

看看这在使用具有不同命名方案的不同编译器编译的动态库之间是如何工作的会很有趣。

【讨论】:

  • 它不适用于不同的编译器,除非它们有意使用相同的 ABI,包括修改。这通常适用于任何 C++ 功能。一个编译器甚至不能调用另一个编译器编写的 C++ 修改函数,除非两者就修改达成一致。这是一种流行的 ABI 规范:mentorembedded.github.io/cxx-abi
  • @Xiao,谢谢,我认为该机制可以纯粹用C++实现(而不是运行时库)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-20
  • 2019-11-29
  • 2017-07-23
  • 2011-04-02
  • 2018-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多