The following description assumes that a non-detached (sub-)thread T1 throws an exception E that is not caught. Another thread T2, possibly the main thread, executes a join for T1. Subsequently, T1 is terminated and E is thrown from inside the join function in T2.
T2 "steals" the exception from T1 by means of a special language-support library function (name subject to change)
std::rethrow_from_foreign_thread(thread_id_type id)That function copies the currently handled exception E from T1 to T2 and reactivates it (15.1p7).
Thus, the library support code for T1 might look like this:
try { // call user-supplied function that is executed as T1 } catch(...) { // wait for condition "about to join me" // acknowledge join, write "exception pending" // exception is "stolen" by T2 // wait for condition "theft complete" // do not use "throw;" return; // T1 terminates } // wait for condition "about to join" // acknowledge join, write "no exception" return; // T1 terminatesThe library support code for join (in T2) might look like this:
void join(thread_id_type id) { // lock inter-thread synchronization mutex // write "id" to global synchronization area // signal condition variable "about to join" // wait for join acknowledge if (exception_pending(id)) { try { std::rethrow_from_foreign_thread(id); } catch (...) { // acknowledge "theft complete" throw; } } // no exception pending, done }This model yields global serialization for join operations, other solutions that use well-defined per-thread memory for synchronization are also feasible.
std::rethrow_from_foreign_thread(thread_id_type id);Precondition: "id" refers to an existing, not-yet-joined thread. That thread has a currently handled exception (15.3p8) and is waiting on a condition variable.
Effect: Reactivates the currently handled exception from that thread in the context of the caller (see 15.1p7).
Note: It is unspecified whether this function invokes the copy constructor of the exception's class (15.1p5).
The std::type_info information could be extended to contain a pointer to the copy constructor. (That pointer would not be exposed outside of the compiler's runtime support, keeping in mind that std::type_info is used in this presentation only for exposition purposes.) The copy constructor always exists and is accessible for types thrown as exceptions (15.1p5).
std::rethrow_from_foreign_thread then copies the exception from the (possibly thread-specific) memory location for "currently handled exceptions" to the local thread's space for the same and performs as if "throw;" would have been written.
If the overhead of extending std::type_info (or the compiler's equivalent) with a pointer to the copy constructor, if any, is deemed excessive, the compiler could hook on all "throw" expressions and emit copy constructor pointers for types that are actually thrown as exceptions. However, this would require additonal machinery to look up said pointer from std::rethrow_from_foreign_thread.
As a third option, the compiler could augment its exception infrastructure to not only transmit the exception object and its corresponding std::type_info, but a copy constructor pointer as well when an exception is actually thrown.
void * currently_handled_exception(); class type_info { // ... void* clone_exception(const void *); void destroy(void *); void rethrow(const void *); };and introducing
typeid(...)
that would return a reference to
the std::type_info of the currently handled exception inside a catch(...)
handler.
If the type identified by a given std::type_info instance does not have a
publicly accessible copy constructor, clone_exception
returns a
null pointer.
Thus, the library support code for T1 might look like this:
try { // call user-supplied function that is executed as T1 } catch(...) { const std::type_info& ti = typeid(...); void * exc = ti.clone_exception(std::currently_handled_exception()); // move "&ti" to joining thread T2 // move "exc" pointer to joining thread T2 return; // T1 terminates } // indicate "no exception" to joining thread return; // T1 terminatesThe library support code for join (in T2) might look like this:
void join(thread_id_type id) { // get the "exc" and "type_info" pointers const void * exc = ...; // get from T1 if (exc) { const std::type_info * ti = ...; // get from T1 try { ti->rethrow(exc); } catch(...) { ti->destroy(exc); throw; } } // no exception pending, done }(Similar inter-thread co-ordination as outlined for option 1 may be necessary.)
Language support library (clause 18):typeid(...)
shall appear lexically inside an exception handler declared withcatch(...)
only. In such use, the result refers to a std::type_info object representing the type of the currently handled exception.
void * currently_handled_exception();Effect: Returns a pointer to the currently handled exception (15.3p8), or a null pointer if there is none.
class type_info { // ... void* clone_exception(const void *); void destroy(void *); void rethrow(const void *); };
void* clone_exception(const void * p);Precondition: The parameter p has a value that was previously returned by the function
currently_handled_exception
.
Effect: Allocates memory and invokes the copy constructor on the
object that p
points to. It is unspecified whether the memory is on
the heap or some other memory accessible to all threads.
Returns: Returns a pointer to a copy of the object that
p
points to, or a null pointer if p
is a null pointer.
Throws: bad_alloc
if memory could not be allocated, or
bad_clone
if the class of the object that p
points to
has no publicly accessible copy constructor.
void destroy(void * p);Precondition: The parameter p has a value that was previously returned by the function clone_exception.
Effect: Invokes the destructor of the object that p
points to and frees the associated memory. Does nothing if p
is a
null pointer.
void rethrow(const void *);Precondition: The parameter p has a value that was previously returned by the function clone_exception.
Throws: The object that p
points to, or nothing if
p
is a null pointer.