views:

297

answers:

6

Hi,

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?

+7  A: 

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
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
+8  A: 

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
Answers should be reversed, vectors -> no and lists -> yes as the question is "Is there any guarantee that these pointers remain valid?"
Naveen
@Naveen - Edited as pointed out, thanks.
DumbCoder
A `deque` may also be a good choice, if random access and no re-allocation upon adding elements is wanted.
mxp
@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
@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
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
@ David Rodríguez-dribeas I don't think the standard specifies so. Other than swapping I don't see any way to reduce the capacity of an vector.
DumbCoder
@David Rodríguez Thanks for citing the standard about deque references!
MartinStettner
+1  A: 

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
Tom
I would strongly advice against storing naked pointers in a STL container. It's a recipe for leaks.
sbi
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
Thanks for the edit (on iPad and can't format or put semi-colons in).
Tom
Then use smart pointers
Tom
I agree with sbi. Using `shared_ptr<>` instead should not have this downside.
mxp
@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
@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.
mxp
@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
@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...
mxp
@sib there is no such button when on the iPad - not sure why
Tom
@Tom: Just indent code with four spaces then.
sbi
@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
+2  A: 

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
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
@MartinStettner: You can easily turn indexes into pointers for a vector. I have expanded my answer to explain so.
sbi
@sbi 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
+2  A: 

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: http://stackoverflow.com/questions/1658956/c-deques-iterator-invalidated-after-push-front/1659688#1659688

Sjoerd
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
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)?
mxp
@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
@mxp: see this answer: http://stackoverflow.com/questions/1658956/c-deques-iterator-invalidated-after-push-front/1659688#1659688
Sjoerd
@Sjoerd That's interesting, thanks!
mxp
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
+1  A: 

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
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