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?
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.
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.
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.
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