2

In the comments on Andrzej's move constructor article, I posted that a moved from object can have any member function called on it that does not have a pre-condition. I gave the example std::vector::front as a function that you cannot call on a moved-from std::vector because it has the pre-condition that the vector is not empty. I gave the examples of std::vector::empty, std::vector::push_back, and std::vector::reserve as functions that you can (but shouldn't) call on a moved-from std::vector, because they have no pre-conditions.

However, that got me thinking. std::vector::push_back requires that there is enough contiguous memory available on the host system. This isn't so much a requirement on the std::vector object as it is about the system it is running on, but that still seems to me to be a pre-condition.

What is the context of move constructors leaving an object in a valid but unspecified state, and does it apply to potential out-of-memory situations with std::vector::push_back? In particular, if std::vector::push_back would have worked prior to a move, is it guaranteed to work after (ignoring issues such as other processes using up memory)?

For reference: § 17.6.3.1

Table 20 — MoveConstructible requirements [moveconstructible]
Expression  Post-condition
T u = rv;   u is equivalent to the value of rv before the construction
T(rv)       T(rv) is equivalent to the value of rv before the construction
rv’s state is unspecified [ Note:rv must still meet the requirements of the library compo-
nent that is using it. The operations listed in those requirements must work as specified
whether rv has been moved from or not. — end note ]
David Stone
  • 26,872
  • 14
  • 68
  • 84
  • 2
    A `push_back` on a moved-from `vector` is legal but pointless. The `vector` is in a valid-but-unspecified state both before and after the `push_back`, except that if successful, you now know what the last element is. – Howard Hinnant Jan 05 '13 at 15:46
  • I completely agree with you (and that was emphasized a little bit stronger in my post on the blog), I was just wondering from a strict language point of view. – David Stone Jan 05 '13 at 17:15

2 Answers2

3

If there is insufficient memory, then push_back exits with a std::bad_alloc exception and the post-condition does not apply. The vector object is left in its original state.

When a vector reaches capacity, this is what happens:

  1. Allocate a bigger block. If this throws a bad_alloc exception, pass it along to the user. Nothing was ever modified.
  2. If the type is not movable, or movable but the move constructor may throw an exception, then copy the elements of the sequence into the bigger block. If a constructor throws an exception, destroy everything already in the bigger block, then free it, then rethrow the exception.
  3. If the type is movable and the move constructor is noexcept, then move the elements into the bigger block. This must succeed. If it the noexcept spec is violated, then the implementation doesn't have to try to move things back (which could, and likely would, also fail.)
  4. Destroy the original memory block and retain the new one.

"Valid but unspecified" doesn't have any deeper meaning in this context. The user simply isn't supposed to make an assumption about what's in it. But inspecting it to discover the contents, or ignoring the contents and adding more, are fine.

Logically, any object should be left empty or in its original state. I seem to recall that vector is actually specified to be left empty, and many programmers assume so, correct or not.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    I don't think `vector` is specified to be left empty. For GCC's `forward_list` I intentionally _don't_ empty the rvalue list following a move assignment in which the storage isn't moved (i.e. the allocator doesn't propagate or the allocators are non-equal) so that the list's nodes aren't deallocated and can be reused by a subsequent assignment. For GCC's `vector` it is left empty (because doing a `clear()` on the rvalue only destroys elements and doesn't deallocate the memory) but I don't think that's required by the standard. – Jonathan Wakely Jan 06 '13 at 14:49
3

What is the context of move constructors leaving an object in a valid but unspecified state, and does it apply to potential out-of-memory situations with std::vector::push_back?

No, it doesn't apply. Having enough memory to do a push_back is not a precondition. It's OK to call push_back when the system has no more memory (in general it's not possible for the program to know in advance if the allocation will succeed or not) and if there isn't enough memory you get an exception. That's just the normal behaviour of push_back not a precondition violation.

In particular, if std::vector::push_back would have worked prior to a move, is it guaranteed to work after (ignoring issues such as other processes using up memory)?

It's legal to attempt to push_back but it's not guaranteed to work. After a move the vector's storage might have been moved to the target of the move, and push_back could cause a reallocation which could fail and throw bad_alloc.

But ignoring allocation failure, the push_back will succeed, and as Howard's comment says, now your vector which previously had an unknown number of elements has an unknown number plus one. Valid but not very useful.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521