Reputation: 117771
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.
Upvotes: 9
Views: 940
Reputation: 21317
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.
Upvotes: 1
Reputation: 42564
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&) {}
}
Upvotes: 8