tags:

views:

120

answers:

4

Say I have an object:

struct Foo 
{
    int bar_;
    Foo(int bar) bar_(bar) {}
};

and I have an STL container that contains Foos, perhaps a vector, and I take

// Elsewhere...

vector<Foo> vec;

vec.push_back(Foo(4));

int *p = &(vec[0].bar_)

This is a terrible idea, right?

The reason is that vector is going to be storing its elements in a dynamically allocated array somewhere, and eventually, if you add enough elements, it will have to allocate another array, copy over all the elements of the original array, and delete the old array. After that happens, p points to garbage. This is why many operations on a vector will invalidate iterators.

It seems like it would be reasonable to assume that an operation that would invalidate iterators from a container will also invalidate pointers to data members of container elements, and that if an operation doesn't invalidate iterators, those pointers will still be safe. However, many reasonable assumptions are false. Is this one of them?

+8  A: 

The standard specifies when such pointers are invalidated. References into a vector die when you increase its size past capacity or add/remove a preceding element. References into a deque are invalidated if you add/remove from the middle.

Otherwise, references and iterators are safe to keep for the lifespan of the underlying object.

Potatoswatter
references into a `deque` are also invalidated when you add to the middle...
rlbond
Good catch. Same applies to `vector`.
Potatoswatter
'iterators into a list may be invalidated by splicing' what do you mean here? Iterators into a list are only invalidated when the element they refer to is erased from the containing list, and at that particular instant, references into those elements are also invalidated. The same goes for the tree case, what do you mean by 'relinking'? Iterators into map or sets are invalidated when the element they refer to is removed from the tree structure, just like references into those objects. Rebalancing of the tree does not affect iterator validity.
David Rodríguez - dribeas
@David: §23.2.2.4 says `list::splice` invalidates all iterators to elements that change ownership. You are right about associative containers, though, 23.1.2/8. I don't know where I got that idea.
Potatoswatter
That was my point: §23.2.2.4 is quite clear in the wording: _"invalidates ... iterators and **references** to"_. Whenever an iterator is invalidated the reference to the element is also invalidated in all containers. Iterators are not different from references to this respect. And in each case, the elements whose iterators/references are invalidated are those that are removed from the list.
David Rodríguez - dribeas
@David: Ah, I see that now, sorry. However, `list::splice` shouldn't (can't?) actually do anything to touch those objects… hmm, N3035 requires the pointers and iterators remain valid, albeit having been "teleported". Guess it's not worth a mention at all.
Potatoswatter
For what it's worth, in the past a `list` implementation could mark its iterators as belonging to a particular container in debug builds, causing them to be invalidated, whereas the pointer was solid despite the standard's allowance for change. I guess now `splice` has to re-mark the iterators and break the performance guarantee when debugging. Well, at least I learned something XvP
Potatoswatter
@Potatocorn: N3035 is a draft for the future standard. I have not read the details of containers there (more interested in threading and lambdas, that are much newer features).
David Rodríguez - dribeas
About the last comment, I guess you are referring to Dinkumware/VS implementations of STL, where the iterators are marked as 'invalid' and exceptions will be thrown when they are used. The mark is an implementation detail: whether it is present or not, the iterator is invalid in both cases, with the difference that in that implementation, the undefined behavior on iterator use is that an exception is thrown (which is just as undefined as yielding a random value, just much better for debugging purposes :)) --BTW, you did really earn +1
David Rodríguez - dribeas
+1  A: 

If the vector is resized, the contents will effectively be recreated by copying and/or assignment. The new contained objects will (probably) be at different locations, and so any pointers to the, or to their members will (probably) be invalidated - you should certainly assume that this is the case.

anon
A: 

Your assumption is reasonable for the vector, because an iterator can be implemented as a thin wrapper around a pointer. As an alternative to pointers, you can store offsets to a vector. These would be invalidated by insertion and erasure, but not by memory reallocation.

Don Reba
+1  A: 

Yep, your instincts are correct. Where the standard mentions that an iterator is invalidated, it also tends to state that references are invalidated too.

For example, here's some partial text describing some of the effects of vector's "reserve" member function:

  Notes:
    Reallocation invalidates all the references, pointers, and iterators
    referring to the elements in the sequence. 

I can't imagine any member functions of any container that would invalidate an iterator to particular elements, but not pointers or references to those same elements, but I suppose I could be mistaken.

Best to check what the standard says for your particular container and member function.

janks