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?
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++.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With