Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does std::async "store" an arbitrary exception?

I am not able to understand how is it possible for std::async to store any exception, not just something derived from std::exception. I played around with the code below

#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
    }
}

I launch f() asynchronously, then throw an int inside f. Magically, this int is caught and stored by the future returned by std::async. I understand that is possible to catch(...) the exception in std::async, but how can the latter store it without knowing the exception type? The exception is not derived from some base class (in this case one perhaps can "clone" it via some Base::clone), but can be any exception. Can we somehow magically "deduce" the exception type?

To summarize, my question is:

How can we store an arbitrary exception inside an object then re-throw it at some later time, without knowing the exception type?

like image 413
vsoftco Avatar asked Feb 23 '15 22:02

vsoftco


2 Answers

std::async could be implemented on top of std::thread and std::packaged_task.

std::packaged_task could be implemented (partly) on top of std::exception_ptr and related function (except that thread-exit ready function).

std::exception_ptr and related functions cannot be written in C++.

like image 66
Yakk - Adam Nevraumont Avatar answered Oct 14 '22 21:10

Yakk - Adam Nevraumont


I'm not sure if this exactly answers your question, but this example might be helpful.

I compiled the following:

int main()
{
    throw 1;
}

with the command

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

The gimple (gcc's intermediate output) is:

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;
}

So it calls __cxa_throw with the address of the a symbol that represents a type. In this case the type is _ZTIi, which is the mangled type of an integer.

Types not available at compile time

The type symbols only need to be available at run time. In a dynamic library that is trying to hide as many symbols as it can, it needs to make sure that any exceptions that aren't caught and dealt with internally are available. For more info, see https://gcc.gnu.org/wiki/Visibility, particularly the section Problems with C++ exceptions (please read!).

It would be interesting to see how this worked between dynamic libraries compiled with different compilers that had different naming schemes.

like image 37
Xiao Avatar answered Oct 14 '22 22:10

Xiao