Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 448
orlp Avatar asked Nov 13 '14 05:11

orlp


People also ask

Does vector have destructor?

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.

Does STD vector call destructor?

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

Can destructors throw?

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.

Can we throw exception from destructor in C++?

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.


2 Answers

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&) {}
}
like image 173
Casey Avatar answered Oct 19 '22 19:10

Casey


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.

like image 24
Rapptz Avatar answered Oct 19 '22 19:10

Rapptz