...
- When the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception, calls a function that exits via an exception. ([except.throw], paragraph 7)
- When a throw-expression with no operand attempts to rethrow an exception and no exception is being handled. ([except.throw], paragraph 9)
- When the exception handling mechanism cannot find a handler for a thrown exception. ([except.handle], paragraph 9)
- When the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception. ([except.spec], paragraph 9)
- See ERR37-CPP. Honor exception specifications for more information
- When the destruction of an object during stack unwinding terminates by throwing an exception. ([except.ctor], paragraph 3)
- See DCL40-CPP. Destructors and deallocation functions must be declared noexcept for more information.
- When initialization of a non-local variable with static or thread storage duration exits via an exception. ([basic.start.init], paragraph 6)
- See ERR41-CPP. Constructors of objects with static or thread storage duration must not throw exceptions for more information.
- When destruction of an object with static or thread storage duration exits via an exception. ([basic.start.term], paragraph 1)
- When execution of a function registered with
std::atexit()
orstd::at_quick_exit()
exits via an exception. ([support.start.term], paragraphs 8 and 12) - When the implementation’s default unexpected exception handler is called. ([except.unexpected], paragraph 2) Note that
std::unexpected()
is currently deprecated. - When
std::unexpected()
throws an exception which is not allowed by the previously violated dynamic-exception-specification, andstd::bad_exception()
is not included in that dynamic-exception-specification. ([except.unexpected], paragraph 3) - When the function
std::nested_exception::rethrow_nested()
is called for an object that has captured no exception. ([except.nested], paragraph 4) - When execution of the initial function of a thread exits via an exception. ([thread.thread.constr], paragraph 5)
- When the destructor is invoked on an object of type
std::thread
that refers to a joinable thread. ([thread.thread.destr], paragraph 1) - When the copy assignment operator is invoked on an object of type
std::thread
that refers to a joinable thread. ([thread.thread.assign], paragraph 1) - When calling
condition_variable::wait()
,condition_variable::wait_until()
, orcondition_variable::wait_for()
results in a failure to meet the post-condition:lock.owns_lock() == true
orlock.mutex()
is not locked by the calling thread. ([thread.condition.condvar], paragraphs 11, 16, 21, 28, 33, and 40) - When calling
condition_variable_any::wait()
,condition_variable_any::wait_until()
, orcondition_variable_any::wait_for()
results in a failure to meet the post-condition:lock
is not locked by the calling thread. ([thread.condition.condvarany], paragraphs 11, 16, and 22)
...
In this noncompliant code example, the call to C::f()
may result in thread entrypoint function thread_start()
does not catch exceptions thrown by throwing_func()
. If the initial thread function exits due to an exception being thrown. Since C::f()
is called from a destructor, this can result in a call to , std::terminate()
if a non-local object of type C
has static or thread storage duration, or if an object of type C
is destroyed during stack unwinding (as in this example) is called.
Code Block | ||||
---|---|---|---|---|
| ||||
class C { void f() noexcept(false); public: ~C() { f(); } }; #include <thread> void throwing_func() noexcept(false); void f() noexcept(falsethread_start(void) { C c; throwing_func(); } void gf() noexcept(true) { try { f(std::thread t(thread_start); } catch (...) { // Handle error } } |
If throwing_func()
throws an exception, f()
does not attempt to catch it and it will be handled by g()
. However, during stack unwinding to reach the exception handler in g()
, the automatic local variable c
will be destroyed, resulting in a call to C::~C()
. When the destructor attempts to throw an exception, std::terminate()
will be called instead of throwing.
Note, the declaration for C::~C()
does not comply with DCL40-CPP. Destructors and deallocation functions must be declared noexcept because destructors are implicitly declared noexcept(true)
, and this destructor allows exceptions by virtue of calling a function marked noexcept(false)
.
t.join();
} |
Compliant Solution
In this compliant solution, the destructor for C
handles thread_start()
handles all exceptions and does not rethrow. When the automatic local variable c
is destroyed, no exception is triggered from the C::~C()
call, and the exception thrown by throwing_func()
will be caught by the handler in g()
., allowing the thread to terminate normally:
Code Block | ||||
---|---|---|---|---|
| ||||
class C { void f#include <thread> void throwing_func() noexcept(false); public: void ~Cthread_start(void) { try { fthrowing_func(); } catch (...) { // Handle error } } }; void throwing_func() noexcept(false); void f() noexcept(false) { C c; throwing_func(); } void g() noexcept(true) { try { f(); } catch (...) { // Handle error }std::thread t(thread_start); t.join(); } |
Noncompliant Code Example
...