May the elements in a std::vector have a throwing destructor?

876 views Asked by At

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.

2

There are 2 answers

0
Casey On BEST ANSWER

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&) {}
}
4
Rapptz On

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() returns true

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.