tags:

views:

275

answers:

7

Since vector elements are stored contiguously, I guess it may not have the same address after some push_back's , because the initial allocated space could not suffice.

I'm working on a code where I need a reference to an element in a vector, like:

int main(){
    vector<int> v;
    v.push_back(1);
    int *ptr = &v[0];
    for(int i=2; i<100; i++)
        v.push_back(i);
    cout << *ptr << endl; //?
    return 0;
}

But it's not necessarily true that ptr contains a reference to v[0], right? How would be a good way to guarantee it?

My first idea would be to use a vector of pointers and dynamic allocation. I'm wondering if there's an easier way to do that?

PS.: Actually I'm using a vector of a class instead of int, but I think the issues are the same.

+9  A: 

You can increase the capacity of the underlying array used by the vector by calling its reserve member function:

v.reserve(100);

So long as you do not put more than 100 elements into the vector, ptr will point to the first element.

James McNellis
Since `reserve` reserves at least as much as you ask for, you can put up to `capacity` elements into the vector.
Bill
The problem now is that I don't know how much space will be used. Is that a good idea to use a sufficiently high value? In some cases, only 10^6 would be safe, but in other cases 100 would be enough.
kunigami
If you don't know how many elements you're going to store, reserving is a BAD idea! You're just postponing your dangling pointer bug till later, when the vector exceeds that capacity!
AshleysBrain
If you don't know in advance how many elements you are going to insert, either use an index into the container or a smart pointer like Jerry Coffin suggests.
James McNellis
+5  A: 

How would be a good way to guarantee it?

std::vector<T> is guaranteed to be continous, but the implementation is free to reallocate or free storage on operations altering the vector contents (vector iterators, pointers or references to elements become undefined as well).

You can achieve your desired result, however, by calling reserve. IIRC, the standard guarantees that no reallocations are done until the size of the vector is larger than its reserved capacity.

Generally, I'd be careful with it (you can quickly get trapped…). Better don't rely on std::vector<>::reserve and iterator persistence unless you really have to.

Alexander Gessler
+2  A: 

As James McNellis and Alexander Gessler stated, reserve is a good way of pre-allocating memory. However, for completeness' sake, I'd like to add that for the pointers to remain valid, all insertion/removal operations must occur from the tail of the vector, otherwise item shifting will again invalidate your pointers.

slyfox
Invalidation will depend on the use of the pointers. If `int *ptr = ` is meant to represent a specific item in the list, then yes, it could be invalidated, but if it is meant to represent the first item in the list, whatever it is, then it will not be invalidated.
Bill
You're right, of course. I assumed that the use-case was to refer to a specific element in the list, since the converse is much easier to deal with using the methods available to you to retrieve items at specific indices.
slyfox
+1  A: 

Depending on your requirements and use case, you might want to take a look at Boost's Pointer Container Library.

In your case you could use boost::ptr_vector<yourClass>.

JRL
+3  A: 

Another possibility possibility would be a purpose-built smart pointer that, instead of storing an address would store the address of the vector itself along with the the index of the item you care about. It would then put those together and get the address of the element only when you dereference it, something like this:

template <class T>
class vec_ptr { 
    std::vector<T> &v;
    size_t index;
public:
    vec_ptr(std::vector<T> &v, size_t index) : v(v), index(index) {}

    T &operator*() { return &v[index]; }
};

Then your int *ptr=&v[0]; would be replaced with something like: vec_ptr<int> ptr(v,0);

A couple of points: first of all, if you rearrange the items in your vector between the time you create the "pointer" and the time you dereference it, it will no longer refer to the original element, but to whatever element happens to be at the specified position. Second, this does no range checking, so (for example) attempting to use the 100th item in a vector that only contains 50 items will give undefined behavior.

Jerry Coffin
+1  A: 

If you don't need your values stored contiguously, you can use std::deque instead of std::vector. It doesn't reallocate, but holds elements in several chunks of memory.

Tadeusz Kopec
+6  A: 

Don't use reserve to postpone this dangling pointer bug - as someone who got this same problem, shrugged, reserved 1000, then a few months later spent ages trying to figure out some weird memory bug (the vector capacity exceeded 1000), I can tell you this is not a robust solution.

You want to avoid taking the address of elements in a vector if at all possible precisely because of the unpredictable nature of reallocations. If you have to, use iterators instead of raw addresses, since checked STL implementations will tell you when they have become invalid, instead of randomly crashing.

The best solution is to change your container:

  • You could use std::list - it does not invalidate existing iterators when adding elements, and only the iterator to an erased element is invalidated when erasing
  • If you're using C++0x, std::vector<std::unique_ptr<T>> is an interesting solution
  • Alternatively, using pointers and new/delete isn't too bad - just don't forget to delete pointers before erasing them. It's not hard to get right this way, but you have to be pretty careful to not cause a memory leak by forgetting a delete. (Mark Ransom also points out: this is not exception safe, the entire vector contents is leaked if an exception causes the vector to be destroyed.)
  • Note that boost's ptr_vector cannot be used safely with some of the STL algorithms, which may be a problem for you.
AshleysBrain
Pointers with new/delete present a problem when a vector is destroyed when you don't expect it - because of a thrown exception for example.
Mark Ransom
Good point, editing.
AshleysBrain