43

If I swap two vectors, will their iterators remain valid, now just pointing to the "other" container, or will the iterator be invalidated?

That is, given:

using namespace std;
vector<int> x(42, 42);
vector<int> y;
vector<int>::iterator a = x.begin(); 
vector<int>::iterator b = x.end();

x.swap(y);

// a and b still valid? Pointing to x or y?

It seems the std mentions nothing about this:

[n3092 - 23.3.6.2]

void swap(vector<T,Allocator>& x);

Effects: Exchanges the contents and capacity() of *this with that of x.

Note that since I'm on VS 2005 I'm also interested in the effects of iterator debug checks etc. (_SECURE_SCL)

Martin Ba
  • 37,187
  • 33
  • 183
  • 337

4 Answers4

32

The behavior of swap has been clarified considerably in C++11, in large part to permit the Standard Library algorithms to use argument dependent lookup (ADL) to find swap functions for user-defined types. C++11 adds a swappable concept (C++11 §17.6.3.2[swappable.requirements]) to make this legal (and required).

The text in the C++11 language standard that addresses your question is the following text from the container requirements (§23.2.1[container.requirements.general]/8), which defines the behavior of the swap member function of a container:

Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap.

It is unspecified whether an iterator with value a.end() before the swap will have value b.end() after the swap.

In your example, a is guaranteed to be valid after the swap, but b is not because it is an end iterator. The reason end iterators are not guaranteed to be valid is explained in a note at §23.2.1/10:

[Note: the end() iterator does not refer to any element, so it may be invalidated. --end note]

This is the same behavior that is defined in C++03, just substantially clarified. The original language from C++03 is at C++03 §23.1/10:

no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.

It's not immediately obvious in the original text, but the phrase "to the elements of the containers" is extremely important, because end() iterators do not point to elements.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 3
    The part with the end() iterators is really a bit crappy in the std, because as this example shows I'd have to introduce special checking logic for all iterators that may be end(). On the other hand, since it just works in VC++ I think I can live with this for now. – Martin Ba Nov 08 '10 at 16:20
  • @Martin: Correctly swapping end iterators is easy for `vector`, since the end iterator is usually just a pointer one-past-the-end of the underlying array; it's not nearly so simple for, say, `map` iterators. – James McNellis Nov 08 '10 at 16:24
  • 1
    @James: I do think it should be possible at lesser cost. I wish the standard was more permissive on this point, it's very tricky that `vector.erase(vector.find(1))` should provoke undefined behavior if `1` is not in `vector` for example. It's annoying that end iterators are second-class iterators in a number of requirements, it really makes for spurious code and hard to spot bugs. – Matthieu M. Nov 08 '10 at 17:42
  • @Matthieu: I do agree that it would be helpful if some member functions (like `erase`) were no-ops when given an end iterator, though then you have a performance hit of having to test whether `it == end` every time you call those member functions (of course, erasing an element should be substantially more expensive than that test). I still think it's possible you could have a container where you can't easily swap end iterators though. – James McNellis Nov 08 '10 at 18:10
  • 1
    @James: I suppose so too, I just can't think of one right now. I did think about the performance impact on STL debug builds, where each iterator has a pointer to its container. It means that a swap becomes a O(M+N) operation where M is the number of iterators in the first container and N in the second. – Matthieu M. Nov 09 '10 at 07:13
  • @Matthieu: You could alleviate that by dynamically allocating an "identity key" that the container and all its iterators refer to; then you can just swap the key along with the data (similar, I suppose, to how `shared_ptr` allocates a reference count object). I have no idea whether any implementation actually does this, though I'm not sure how they could otherwise find all the iterators to update them (I haven't thought about that much; maybe there's a way). – James McNellis Nov 09 '10 at 16:30
  • 1
    @James: In stl-debug mode, the container keeps a list of all the iterators it created (and their copy), similar to the Observer Pattern. This is then used to notify the iterators in case of modification of the container. Depending on the implementation, it might cause an impressive slow-down, Dirkumware STL used to be 10x slower... I think gcc's was better, but the factor is still non-negligible. – Matthieu M. Nov 10 '10 at 07:36
13

Swapping two vectors does not invalidate the iterators, pointers, and references to its elements (C++03, 23.1.11).

Typically the iterator would contain knowledge of its container, and the swap operation maintains this for a given iterator.

In VC++ 10 the vector container is managed using this structure in <xutility>, for example:

struct _Container_proxy
{   // store head of iterator chain and back pointer
    _Container_proxy()
    : _Mycont(0), _Myfirstiter(0)
    {   // construct from pointers
    }

    const _Container_base12 *_Mycont;
    _Iterator_base12 *_Myfirstiter;
};
Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • 2
    Note that in the n3092 draft the requirement is laid out in §23.2.1/9 : "Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap." – Martin Ba Nov 08 '10 at 15:11
  • Though it's not immediately obvious, the sneaky phrase is "to its elements." See my answer for details. – James McNellis Nov 08 '10 at 15:27
  • Thanks v much for the latest detail @James – Steve Townsend Nov 08 '10 at 15:29
  • 1
    I'm assuming that number is a C++ standard paragraph number? It's a shame that one can't provide a link to such things. I know its a source of income and all, but ISO and company really need to get with the new millenium. – T.E.D. Nov 08 '10 at 15:30
  • @T.E.D.: Yes, that's a reference to C++03. You can download the drafts of the standard free of charge and in PDF form. The latest draft is linked from [the C++0x tag wiki](http://stackoverflow.com/tags/c%2b%2b0x/info). – James McNellis Nov 08 '10 at 15:33
3

All iterators that refer to the elements of the containers remain valid

JoeG
  • 12,994
  • 1
  • 38
  • 63
dubnde
  • 4,359
  • 9
  • 43
  • 63
  • 1
    thanks James McNellis. You explanation in your answer was very helpful to clarify your statement. I have modified my statement in line with that not to learning something. – dubnde Nov 08 '10 at 16:56
1

As for Visual Studio 2005, I have just tested it. I think it should always work, as the vector::swap function even contains an explicit step to swap everything:

 // vector-header
    void swap(_Myt& _Right)
        {   // exchange contents with _Right
        if (this->_Alval == _Right._Alval)
            {   // same allocator, swap control information

 #if _HAS_ITERATOR_DEBUGGING
            this->_Swap_all(_Right);
 #endif /* _HAS_ITERATOR_DEBUGGING */
 ...

The iterators point to their original elements in the now-swapped vector object. (I.e. w/rg to the OP, they first pointed to elements in x, after the swap they point to elements in y.)

Note that in the n3092 draft the requirement is laid out in §23.2.1/9 :

Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap.

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 1
    Note that this exact member function was a shaky area in VS2005, perhaps an SP fixed this : http://connect.microsoft.com/VisualStudio/feedback/details/106322/vs2005-checked-stl-vector-swap-causes-incorrect-iterator-assertion-in-release-builds – Steve Townsend Nov 08 '10 at 15:14