0

If I have an iterator pointing to an element in an STL container, and I moved the element with the iterator, does the standard guarantee that the iterator is still valid? Can I use it with container's method, e.g. container::erase?

Also does it matter, if the container is a continuous one, e.g. vector, or non-continuous one, e.g. list?

std::list<std::string> l{"a", "b", "c"};
auto iter = l.begin();
auto s = std::move(*iter);
l.erase(iter);       // <----- is it valid to erase it, whose underlying element has been removed?
Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
for_stack
  • 21,012
  • 4
  • 35
  • 48
  • I don't seem to be able to cancel my previous duplicate flag and create a new one but after reading the question carefully, this seems to contain the answer: https://stackoverflow.com/questions/7027523/what-can-i-do-with-a-moved-from-object – Piotr Siupa Jul 31 '22 at 07:45
  • 1
    What you have done is valid. After the call of `l.erase()` the iterator `iter` is invalid. – Peter Jul 31 '22 at 07:49

3 Answers3

5

Yes, you've modified the object in the container. You've not modified the container itself so the iterator is still valid

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • OP did modify the container itself. – Piotr Siupa Jul 31 '22 at 07:36
  • @NO_NAME yes, but the question wasn't `is the iterator still valid after erase` it was `is the iterator still valid so that I can pass it to erase` – Alan Birtles Jul 31 '22 at 07:38
  • I agree with Alan. Just not sure if the standard has a guarantee on it. – for_stack Jul 31 '22 at 07:45
  • The `std::move` does nothing but allow the move constructor to be used in `auto s = ...`. The moved from object is in a valid but unspecified state after the move. It still exists and nothing has changed for the container. You can `l.erase(iter)` or `*iter = "d"` or any other operation that does not actually call a member function on the object that isn't valid after a move. – Goswin von Brederlow Jul 31 '22 at 21:30
3

"Moving" an underlying element may not be the best name to use in this context. The name of this operation express the intention behind it but not how it really works.

In fact, the move operation is a form of copy operation with one difference: it is allowed to change the state of the "copied" object if it speeds up the execution. In case of the std::string this means that the internal buffer containing characters may be not deep-copied but just copied by address. The original object has to be then set to an empty state, to tell it to not use this buffer anymore. (Emptying the source string is not guaranteed. Optimizations of std::string are more complicated than I described.)

The important thing is that after the move operation, the original object is still there. It's just not guaranteed to have any specific state.

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
2

In this particular case you've done nothing to the iterator, but much rather to the object within it, so yes: The iterator remains valid.

But if you look at std::list::erase, it sports a line such as "References and iterators to the erased elements are invalidated. Other references and iterators are not affected."

So if you tried to do *iter after erase, it would cause your program to fail.

This may seem obvious for erase, but there are other operations where it is not as obvious.

For std::list for example, the reference page says:

Adding, removing and moving the elements within the list or across several lists does not invalidate the iterators or references. An iterator is invalidated only when the corresponding element is deleted.

For std::vector on the other hand, the reference for the push_back method says:

If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.

That means, unlike with std::list, it is not generally safe to keep an iterator to an element around, if the vector grows (because the underlying storage location of the item changes).

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
Refugnic Eternium
  • 4,089
  • 1
  • 15
  • 24