Search code examples
c++stlinsertcopy-constructorcopy-assignment

fill insert() - copy constructor and copy assignment noexcept status?


  1. Are STL container elements required to have noexcept copy-constructors and copy-assignment operators? Please provide a reference if possible.
  2. If not, what is the state of a STL container when an exception happens during a multi-insert, e.g. during fill insert.

The problem comes up when trying to write a generic wrapper that allows intercepting/vetoing modifications. Any implementation I can come up with is likely to change the semantics of the underlying container unless specialized for every container type (which is not really an option).

For example, std::vector has a fill insert:

void insert (iterator position, size_type n, const value_type& val);

This requires value_type to be both CopyInsertable as well as CopyAssignable. Note that it does not require the value type to be DefaultConstructible.

Edit 3 Stroustrup himself (table on page 956) indicates that multi-element insert is supposed to have the strong guarantee for all of vector, deque, list and map. Meaning that the full standard library operation either succeeds or fails atomically.

Edit 4 However, the guarantee only applies when relevant operations (in this case the copy constructor) do not themselves throw exceptions, which is exactly my problem.

As far as I understand it, this leaves two basic implementation methods:

  1. Create dummy entries for the new elements and copy-assign val. This only works when it is possible to create dummy elements by either copying existing elements in the container or when value_type is DefaultConstructible (which is not a requirement).
  2. Copy-construct elements one after the other directly into their respective locations in the container. This seems to be more or less the canonical implementation.

Edit 2: I won't call this undefined behaviour, because that term seems to red flag people into thinking undefined as by language runtime/the standard.

Both implementations seem to leave the container with unknown contents (i.e. it is not clear what elements the container holds after the exception) when either the copy constructor or the copy-assignment operator raise an exception.

Edit 1: Note that this does not mean I assume there is bad behaviour wrt to C++ runtime, e.g. memory leaks or undefined values. However, it seems it is more or less unspecified what the contents of the container are. In particular, the contents of the container could have been completely (albeit consistently) altered.

As an example, consider a third (hybrid) method:

  1. create a list of n copies of the template object val.
  2. copy-assign the elements from this list into the target container.

The difference is the effect on the container when the copy constructor raises an exception. In this case, the contents of the container remain unchanged if the copy-constructor throws (but still cause unspecified contents when the copy assignment operator throws). When using pointers (i.e. when not using std::vector), the copy-assignment can probably be left out and only the pointers re-arranged, making the operation atomic wrt. exceptions.

As for noexcept on container elements: Objects are created via allocator_traits<value_type>::construct(ptr, args), which isn't noexcept and I also cannot find the requirement that container elements most have a noexcept copy constructor/copy-assigment operator (e.g. std::shared_ptr and std::unique_ptr require this).

Note that automatically generated operations for copy-construct and copy-assign are required to be noexcept unless they are required to call an operation that itself could raise an exception.

This leaves me confused and I'm sure I missed something, but I cannot figure out the part of the standard that might prove me wrong.


Solution

    1. Are STL container elements required to have noexcept copy-constructors and copy-assignment operators? Please provide a reference if possible.

    No, they are not, and I can't provide a reference for a requirement that isn't present, because it isn't present.

    If it was required it would say so in the standard, but it doesn't.

    In general elements are not even required to have a copy constructor at all, a move constructor is enough for most operations. Similarly for copy assignment.

    1. If not, what is the state of a STL container when an exception happens during a multi-insert, e.g. during fill insert.

    It depends on the container, and where in the container you are inserting.

    For node-based containers such as lists if one insertion throws then any elements that were already inserted remain in the container.

    For std::vector exactly what happens depends where in the vector you are inserting and whether a reallocation is needed, and whether the elements have a noexcept move constructor or not. All you can rely on is that there will be no leaks and no partially constructed elements, so the vector is in a valid state.

    Both implementations seem to leave the container in an undefined state (i.e. it is not clear what elements the container holds after the exception) when either the copy constructor or the copy-assignment operator raise an exception.

    That is not an undefined state, it's just a state you don't have full information about. You can use vector::size() and vector::capacity() to determine its state, and inspect the elements at position [0, size()) to check the state of the elements. After that you know everything about the vector's state.

    i.e. the vector is in a valid state at all times, you just don't know enough information to accurately describe that state, until you inspect it.

    The word "undefined" suggests the state is inconsistent, or unknowable, which is not true. The standard containers always give the basic exception-safety guarantee so failed operations will not leave them in an undefined state.

    Even in an extreme case such as vector::push_back() where the element type is not copyable and has a throwing move constructor, an exception will leave the vector in a "unspecified" state, so there are no leaks and no invalid objects and no undefined behaviour.

    As an example, consider a third (hybrid) method:

    • create a list of n copies of the template object val.
    • copy-assign the elements from this list into the target container.

    The difference is the effect on the container when the copy constructor raises an exception. In this case, the contents of the container remain unchanged if the copy-assignment operator throws.

    Maybe I'm misunderstanding, but I don't see how this is any better than "Copy-construct elements one after the other directly into their respective locations in the container." It performs considerably more work, doing N copy constructions plus N copy assignments, instead of N copy constructions, and I don't see any advantage in terms of the eventual state of the container.

    In both cases you need to add n new elements to the container, which could throw, and if it throws I don't see why it makes any difference to the final state whether you were also planning to do some extra assignments afterwards.