When I look at the Container
requirements on cppreference it lists Destructible
as a requirement for value_type
. This seems to imply that destructors of container elements may not throw.
I haven't been able to find a citation for this requirement in the C++14 standard (haven't looked in older versions). The only thing I can find is that value_type
must be Erasable
which doesn't imply any exception safety at all.
So my question is, may the elements in a std::vector
have a throwing destructor? If not, what section in the standard prohibits it?
P.S.: Don't worry, I'm not planning to create types with throwing destructors. I'm just writing a standard-conforming implementation and trying to get exception safety right.
One way of deleting a vector is to use the destructor of the vector. In this case, all the elements are deleted, but the name of the vector is not deleted.
std::vector<T>::clear() always calls the destructor of each element, but the destructor of a pointer is a no-op (or a pointer has a trivial destructor).
Throwing out of a destructor can result in a crash, because this destructor might be called as part of "Stack unwinding". Stack unwinding is a procedure which takes place when an exception is thrown.
The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped.
N4140 [res.on.functions]/2 states:
In particular, the effects are undefined in the following cases:
(2.1) — for replacement functions (18.6.1), if the installed replacement function does not implement the semantics of the applicable Required behavior: paragraph.
(2.2) — for handler functions (18.6.2.3, 18.8.3.1, D.11.1), if the installed handler function does not implement the semantics of the applicable Required behavior: paragraph
(2.3) — for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (17.6.3.5, 23.2, 24.2, 26.2). Operations on such types can report a failure by throwing an exception unless otherwise specified.
(2.4) — if any replacement function or handler function or destructor operation exits via an exception, unless specifically allowed in the applicable Required behavior: paragraph.
(2.5) — if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component.
Which is a bit obscure, but saves a lot of space that would otherwise be wasted on "T
must meet the Destructible requirements" statements throughout the library clauses.
Notably, this does not imply that elements of a std::vector
can't have a throwing destructor; it only means that said destructor must never throw when called from the standard library. So e.g. this program is conforming:
#include <vector>
struct A {
bool throw_an_int = false;
~A() noexcept(false) {
if (throw_an_int) throw 42;
}
};
int main() {
try {
A a;
a.throw_an_int = true;
std::vector<A> lots_of_As(42);
} catch(int&) {}
}
Yes.† The standard says this in general requirements:
[C++11: §23.2.1/10]:
Unless otherwise specified (see 23.2.4.1, 23.2.5.1, 23.3.3.4, and 23.3.6.5) all container types defined in this Clause meet the following additional requirements:
— no erase(), clear(), pop_back() or pop_front() function throws an exception.
Using the clear
function as an example (due to it not being an exception to the general requirement) it has the following requirements:
Destroys all elements in a. Invalidates all references, pointers, and iterators referring to the elements of a and may invalidate the past-the-end iterator. Post:
a.empty()
returnstrue
Which means that it essentially calls the std::allocator_traits<Alloc>::destroy
on all elements. Which delegates over to t->~T()
if a.destroy(t)
is unavailable. However this implicitly guarantees that neither a.destroy(t)
nor t->~T()
should throw because it'll violate clear
's strong noexcept
specification:
// § 23.3.6.1
void clear() noexcept;
So through deduction we could assert that destructors can throw but they have to be suppressed through some mechanism such as wrapping them in a try-catch block.
†: Upon further inspection, it seems that destructors can throw but the exceptions have to be suppressed as stated in the comments below.
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