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

dhke
  • 15,008
  • 2
  • 39
  • 56
  • 1
    `insert` only gives the basic exception guarantee ("no leaks"). If one of the copy constructors throws, the vector is *not* in an undefined state. It will contain the number of objects given by `size`. – Tobias Brandt Aug 24 '15 at 10:32
  • @TobiasBrandt There's no guarantee for that. The vector is consistent and contains proper elements, but what these elements are in what order is more or less unspecified/depends on implementation. – dhke Aug 24 '15 at 10:56
  • No, the order is stable. There is no way an insertion can reorder existing elements. – Jonathan Wakely Aug 24 '15 at 10:58
  • @JonathanWakely I'm not so sure if this is true for every $customcontainer that adheres to the container requirements. I'm think of e.g. a priority queue which allows for duplicate elements where the insert might actually be re-ordering the elements. I was looking for a guarantee and we might have established there isn't one, already. – dhke Aug 24 '15 at 11:04
  • You said "the vector", and there is no way for vector to reorder existing elements. Even for other containers the relative order of existing elements is stable. Technically `priority_queue` is not a container, it's a container adaptor, but even so the relative order of existing elements is stable when inserting new elements (for unordered containers that is only true for elements with equivalent keys, but that means duplicates don't get re-ordered relative to each other, see [unord.req]/6) – Jonathan Wakely Aug 24 '15 at 11:08
  • So in short: You end up with a container, where (some of of the) new elements might or might not be in there and you need to manually check which objects made it. Most likely you will have all objects before the one that raised the exception. That does not prevent the container from possibly handing you some random (but valid, e.g. copies of existing) elements, e.g. as artifacts of the capacity expansion. – dhke Aug 24 '15 at 11:18
  • 1
    Yes. Furthermore, the "random but valid elements" case can only happen for some containers (`vector` and `deque`) and never for node-based ones such as `list`. (N.B. priority_queue doesn't support inserting more than one element at a time anyway, so it's irrelevant here, surely)? – Jonathan Wakely Aug 24 '15 at 11:22
  • @JonathanWakely Yes, thank you, I think I got that message from reading the sources, too. It basically boils down to *depending on the type of the container*, which somehow precludes me writing a generic wrapper that preserves semantics of the original container. – dhke Aug 24 '15 at 11:26

1 Answers1

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

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Reading up on this, [Stroustrup](http://www.stroustrup.com/3rd_safe.pdf#956) himself (table on page 956) indicates that *multi-element insert* has the *strong guarantee* for all of vector, deque, list and map. This means, it either succeeds or fails completely. I sense a a contradiction. – dhke Aug 24 '15 at 16:56
  • 1
    Look more carefully, and read the paragraph under the table. It offers the strong guarantee if copying the elements doesn't throw, i.e. if memory allocation fails. If a copy fails (which it can do, as I said right at the start of my answer) then that "strong" doesn't apply. Oh and also since you're not looking at the 4th edition you're looking at the rules for C++03, not C++11. – Jonathan Wakely Aug 24 '15 at 18:11
  • N.B. the _strong_ guarantee applies if the copying doesn't throw. The basic guarantee applies even if copying throws. – Jonathan Wakely Aug 24 '15 at 18:24
  • Thank you again, The situation being as it is, I'll probably take the easy way out and not provide the multi-insert in the decorator at all. – dhke Aug 24 '15 at 18:31