34

I'm having a std::vector with elements of some class ClassA. Additionally I want to create an index using a std::map<key,ClassA*> which maps some key value to pointers to elements contained in the vector.

Is there any guarantee that these pointers remain valid (and point to the same object) when elements are added at the end of the vector (not inserted). I.e, would the following code be correct:

std::vector<ClassA> storage;
std::map<int, ClassA*> map;

for (int i=0; i<10000; ++i) {
  storage.push_back(ClassA());
  map.insert(std::make_pair(storage.back().getKey(), &(storage.back()));
}
// map contains only valid pointers to the 'correct' elements of storage

How is the situation, if I use std::list instead of std::vector?

MartinStettner
  • 28,719
  • 15
  • 79
  • 106
  • 1
    What is the purpose of vector here? Do you need to remember the order in which they have been created? You could use map and vecor instead. Iterators/Pointers/References to elements of a map stay valid longer. See the guarantees of your favorite standard library reference. – sellibitze Jul 20 '10 at 11:52

7 Answers7

29

Vectors - No. Because the capacity of vectors never shrinks, it is guaranteed that references, pointers, and iterators remain valid even when elements are deleted or changed, provided they refer to a position before the manipulated elements. However, insertions may invalidate references, pointers, and iterators.

Lists - Yes, inserting and deleting elements does not invalidate pointers, references, and iterators to other elements

DumbCoder
  • 5,696
  • 3
  • 29
  • 40
  • 2
    Answers should be reversed, vectors -> no and lists -> yes as the question is "Is there any guarantee that these pointers remain valid?" – Naveen Jul 20 '10 at 07:33
  • A `deque` may also be a good choice, if random access and no re-allocation upon adding elements is wanted. – foraidt Jul 20 '10 at 07:46
  • @mxp I do not have a STL standard at hand, but at least the SGI reference site states explicitely, that `deque` iterators are invalidated by any insertion, so that's not an option (see http://www.sgi.com/tech/stl/Deque.html) – MartinStettner Jul 20 '10 at 07:53
  • 8
    @MartinStettner: §23.2.1.3 guarantees that while iterators into the dequeue are invalidated, pointers and references to the elements are still valid: `An insert in the middle of the deque invalidates all the iterators and references to elements of the deque. An insert at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque.` – David Rodríguez - dribeas Jul 20 '10 at 08:06
  • 3
    I have been unable to find a quote from the standard where it guarantees that the capacity of the vector must not shrink --it might not be a direct requirement but an effect of other requirements as the complexity of the algorithms. Can you provide a quote/rationale why the standard requires vector capacities never to shrink? --It is the behavior of all the implementations I know of, but that is not the same as a guarantee from the standard. – David Rodríguez - dribeas Jul 20 '10 at 09:07
  • @David Rodríguez Thanks for citing the standard about deque references! – MartinStettner Jul 20 '10 at 09:52
  • 1
    Just a note; the mention is at *23.3.3.4 #1, p.737* in N3337. – eonil Mar 11 '14 at 11:14
9

As far as I understand, there is no such guarantee. Adding elements to the vector will cause elements re-allocation, thus invalidating all your pointers in the map.

SadSido
  • 2,511
  • 22
  • 36
  • That's what I thought. Do you know about `std::list`? After all, if it's implemented as linked list, there would be no need for reallocation ... – MartinStettner Jul 20 '10 at 07:39
  • 1
    I think *can cause* is more appropriate wording. And even so, I can expect `realloc` being used in the internal implementation, which again *can* break the pointers. – Bartek Banachewicz Mar 11 '14 at 11:43
7

Use std::deque! Pointers to the elements are stable when only push_back() is used.

Note: Iterators to elements may be invalidated! Pointers to elements won't.

Edit: this answer explains the details why: C++ deque's iterator invalidated after push_front()

Community
  • 1
  • 1
Sjoerd
  • 6,837
  • 31
  • 44
  • 2
    Are you sure about this? Is there any part in the C++ standard covering this claim? It might be implemented most of the time in this way but I need some sort of guarantee ... – MartinStettner Jul 20 '10 at 07:57
  • Why should an iterator, which is basically a pointer *specifically designed for that container*, be invalidated, but not a raw pointer, which represents nothing but a memory address (and a type)? – foraidt Jul 20 '10 at 07:57
  • 2
    @mxp: An iterator needs to be able to find the next element. This ability requires additional info in the iterator, and this additional info might be invalidated. – Sjoerd Jul 20 '10 at 08:02
  • @mxp: see this answer: http://stackoverflow.com/questions/1658956/c-deques-iterator-invalidated-after-push-front/1659688#1659688 – Sjoerd Jul 20 '10 at 08:09
  • I added the quote in the standard to [this](http://stackoverflow.com/questions/3287801/pointers-to-elements-of-stdvector-and-stdlist/3287828#3287828) answer as a comment. – David Rodríguez - dribeas Jul 20 '10 at 09:00
  • Solved my problem of spoilt references by replacing vector -> deque, thanx a lot !! – Stepan Yakovenko Nov 16 '15 at 17:02
3

I'm not sure whether it's guaranteed, but in practice storage.reserve(needed_size) should make sure no reallocations occur.

But why don't you store indexes?
It's easy to convert indexes to iterators by adding them to the begin iterator (storage.begin()+idx) and it's easy to turn any iterator into a pointer by first dereferencing it, and then taking its address (&*(storage.begin()+idx)).

sbi
  • 219,715
  • 46
  • 258
  • 445
  • The problem is, that I do not know `needed_size` in advance (I admit that the code is a bit simplified ...) Storing indices would be an option but I also need to pass pointers to various other parts of the program which shouldn't have access to the vector (again the code doesn't show that aspect) – MartinStettner Jul 20 '10 at 07:50
  • @MartinStettner: You can easily turn indexes into pointers for a vector. I have expanded my answer to explain so. – sbi Jul 20 '10 at 07:57
  • 1
    The whole thing is encapsulated into a class which needs to pass pointers to the "outside", other parts of the program also might store these pointers, so they need to be constant. If I used your approach I'd need to provide also the begin() iterator which would be a breach of encapsulation (the vector storage should be an internal implementation detail...). – MartinStettner Jul 20 '10 at 09:45
1

Just make them both store pointers an explicitly delete the objects when you don't need them.

std::vector<ClassA*> storage;
std::map<int, ClassA*> map;

for (int i=0; i<10000; ++i) {
  ClassA* a = new ClassA()
  storage.push_back(a)
  map.insert(std::make_pair(a->getKey(), a))
}
// map contains only valid pointers to the 'correct' elements of storage
sbi
  • 219,715
  • 46
  • 258
  • 445
Tom
  • 43,583
  • 4
  • 41
  • 61
  • 3
    I would strongly advice against storing naked pointers in a STL container. It's a recipe for leaks. – sbi Jul 20 '10 at 07:37
  • Hm, that's exactly what I try to avoid :). I could use only the map in this case (for my problem), I just want to have some container to take care of memory deallocation. – MartinStettner Jul 20 '10 at 07:42
  • Thanks for the edit (on iPad and can't format or put semi-colons in). – Tom Jul 20 '10 at 07:43
  • 3
    I agree with sbi. Using `shared_ptr<>` instead should not have this downside. – foraidt Jul 20 '10 at 07:44
  • @Tom: You can't click on the little `101010` button on iPad? @mxp: Still, that's a dynamic allocation more for each element. @MartinStettner What's wrong with storing the _objects_ themselves (not pointers to them) in the map?? – sbi Jul 20 '10 at 07:47
  • @sbi The object is allocated once only. If you mean the smart pointers, yeah, these have to be allocated, too. But this is not as grave as the risk of memory leaks. – foraidt Jul 20 '10 at 08:02
  • 1
    @mxp: while I stand in your position (the extra allocations would most probably not cause a performance hit unless run in a tight loop), the fact is that vectors perform allocations of memory in chunks and they grow exponentially. This means that the amount of calls to the memory allocator will be logarithmic and not linear with the growth of the vector. If you add a shared pointer that duplicates the amount of allocations needed --unless you use `make_shared`. – David Rodríguez - dribeas Jul 20 '10 at 08:11
  • @David (Thanks for pointing out `make_shared`!) But isn't duplicating a shared pointer leaving the object, to which it points, untouched? Creating an object and a `shared_ptr` to it doubles the amount of allocations, ok. But when resizing the `vector`, only new `shared_ptr`s will be created, not copies of the objects to which they point. I think I'm not getting something... – foraidt Jul 20 '10 at 08:26
  • @sib there is no such button when on the iPad - not sure why – Tom Jul 20 '10 at 08:32
  • @mxp: If you use shared pointers, each element is allocated and that is a call to the allocator. When using a vector, a single allocation will grab memory for many objects, that will later be in-place constructed. If you create a vector and reserve 100 elements, with that single allocation you will be able to push 100 elements into the vector. If you use a pointer/shared_ptr vector that allocation will retrieve space for 100 pointers, but each one of the objects must be allocated in a single allocation of its own. It's not an issue of the vector growth, but element creation. – David Rodríguez - dribeas Jul 20 '10 at 08:59
1

From one of the comments to another answer, it seems as if all that you want is centralize (ease) memory management. If that is really the case, you should consider using prepackaged solutions like the boost pointer container library and keep your own code as simple as possible.

In particular, take a look at ptr_map

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thank you very much for pointing this out. Unfortunately this project is for a large client who doesn't (yet) want to include the boost library into his code (although this would ease a lot of problems :) ...) – MartinStettner Jul 20 '10 at 09:49
0
  1. for vectors no.
  2. for lists yes. how? iterator works as a pointer to a particular node in the list. so you can assign values to any struct like:

    list mylist;

    pair< list::iterator ,int > temp;

    temp = make_pair( mylist.begin() , x );

  • 1
    This is literally the same answer as [DumbCoder's](https://stackoverflow.com/a/3287828/501011), only 7 years too late and worse in every aspect – MechMK1 Dec 17 '17 at 22:13
  • 1
    I had the similar problem and was looking for a simple example. As it was not there in any of the above answers I thought to write an example myself. – Nakul Vaidya Dec 20 '17 at 17:08