Wednesday 16 January 2013

std::current_exception()

Ok, this is a very nice idea, and also very simple. std::current_exception returns a std::exception_ptr to the exception currently being handled by a catch{} block. The following snippet shows it in action quite well, but I don't expect that this kind of thing will take off for current_exception. It might even be something of an antipattern in its current form:
 try  
 {  
  someFunction();  
 }  
 catch(...)  
 {  
  auto exPtr = std::current_exception();  
  /* now we have an exception pointer to the current exception... */  
 }  

This is all well and good, but what's the point? In search of that we look at bit deeper at std::exception_ptr, which provides shared ownership semantics for exceptions in a similar fashion to shared ownership of objects provided by std::shared_ptr. Exception pointers can be created from exceptions:
 try  
 {  
  someFunction()  
 }  
 catch(std::exception &ex)  
 {  
  std::exception_ptr exPtr = std::make_exception_ptr(ex);  
  /* now we have an exception ptr... */
 }  

The C++11 standard says that make_exception_ptr works roughly like this:
 template<class E> exception_ptr make_exception_ptr(E e) noexcept  
 {  
  try   
  {  
   throw e;  
  }   
  catch(...)  
  {  
   return current_exception();  
  }  
 }  

It can't be the case that std::current_exception exists only to service std::make_exception_ptr, so what's all this about? The answer shouldn't surprise you: threading. Exceptions are notoriously bad at cross-thread error propagation and until C++11 there's been no simple cross-platform way of doing this. Sure, you could catch your exception, stash it somewhere and let another thread rethrow it to code that can do something about it, but the problem is that you need to manually account for all types of exception that can be thrown. This doesn't scale well and leaves you responsible for actively managing the lifetime of your exception object.

Using
std::current_exception() to get a  std::exception_ptr to anything that's been thrown (and remember, you can throw just about anything in C++!), you can store a container of those exception pointers in one thread and use std::rethrow_exception to propagate them again in another thread. This is possible because, although the precise definition of std::exception_ptr is unspecified, it is untemplated and therefore opaque enough to store in a homogeneous container. As soon as your exception is no longer referenced by at least one std::exception_ptr, its lifetime ends and the resources are freed up. 

What could be simpler?

The "transporting exception across the thread boundary" pattern is implemented in this example from Microsoft.

No comments:

Post a Comment