Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

can rethrow_exception really throw the same exception object, rather than a copy?

While examining what exception_ptr does, the C++11 standard says (18.8.5/7) that:

Use of rethrow_exception on exception_ptr objects that refer to the same exception object shall not introduce a data race. [ Note: if rethrow_exception rethrows the same exception object (rather than a copy), concurrent access to that rethrown exception object may introduce a data race...

I don't find the case where this weird "Note" applies, since the described effect of rethrow_exception is "Throws: the exception object to which p refers" but 15.1/3, describing the general exception throwing process mandates that "throwing an exception copy-initializes a temporary object, called the exception object."

The weird note would imply that rethrow_exception skips this copy-initialization. But is this really possible?

like image 857
soulie Avatar asked Feb 08 '13 15:02

soulie


3 Answers

When you say throw x;, then the exception object has the same type as x but is a copy.

When you say std::rethrow_exception(p);, the exception object is the actual object which is referred to by the pointer, and no further copies get made.

Thus is several threads concurrently rethrow the same exception pointer (which you are allowed to copy!), then they all have a reference to the same object.

like image 174
Kerrek SB Avatar answered Oct 15 '22 17:10

Kerrek SB


Yes, it looks like a deficiency in the standard. For a rethrowing throw-expression i.e. throw; without an operand, 15.1p8 says:

A throw-expression with no operand rethrows the currently handled exception. The exception is reactivated with the existing exception object; no new exception object is created. [...]

That is:

#include <exception>
#include <cassert>
int main() {
   std::exception *p = nullptr;
   try {
      try {
         throw std::exception();
      } catch(std::exception &ex) {
         p = &ex;
         throw;
      }
   } catch(std::exception &ex) {
      assert(p == &ex);
   }
}

If the implementation of current_exception copies the currently handled exception object, there's no way to tell whether rethrow_exception copies or not, but if it refers to the exception object then we can check:

#include <exception>
#include <iostream>
int main() {
   std::exception_ptr p;
   try {
      try {
         throw std::exception();
      } catch(...) {
         p = std::current_exception();
         std::cout << (p == std::current_exception()) << ' ';
         std::rethrow_exception(p);
      }
   } catch(...) {
      std::cout << (p == std::current_exception()) << '\n';
   }
}

Every implementation I've tried it on prints 1 1; 0 0 is allowed if current_exception copies; 0 1 is obviously impossible, while the standard in its current state appears to require 1 0. The fix would be for 18.8.5p10 to be clarified with language similar to 15.1p8, either allowing or mandating rethrow_exception to not copy the exception object pointed to by the exception_ptr.

Most Throws: specifications in the standard just name a type (Throws: bad_alloc) or use the indefinite article (Throws: an exception of type ...); the only other exception specifications to use the definite article are those of future::get and shared_future::get, so any resolution should probably address those as well.

like image 3
ecatmur Avatar answered Oct 15 '22 15:10

ecatmur


Yes, it's possible. The exception-handling mechanism already has a copy of the object that was originally thrown, squirreled away in a private memory stash. Typically, exception_ptr is implemented as a smart pointer that manages a reference count for that copy.

As to the general requirements, if a specific requirement conflicts with a general requirement, the specific requirement wins.

like image 2
Pete Becker Avatar answered Oct 15 '22 15:10

Pete Becker