11

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.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
kunigami
  • 3,046
  • 4
  • 26
  • 42
  • 7
    Why not get `&v[0]` only when you need it? – kennytm Mar 15 '10 at 13:28
  • 2
    You could keep the index of the element instead of pointer and then to access it using at() or operator[] – Dmitry Yudakov Mar 15 '10 at 13:38
  • @KennyTM Although: it isn't explicited above, in my actual code I can't tell which index to access anymore when I need it. – kunigami Mar 15 '10 at 14:51
  • @Dmitry Yudakov: Great idea! Unfortunately I realized that using this approach would require me to do a lot of changes, since I've already used an structure with pointers in others parts of the code, but without problems since I was pre-allocating the vector. – kunigami Mar 15 '10 at 14:51

8 Answers8

17

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
  • 22,335
  • 15
  • 88
  • 124
9

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
  • 348,265
  • 75
  • 913
  • 977
  • 1
    Since `reserve` reserves at least as much as you ask for, you can put up to `capacity` elements into the vector. – Bill Mar 15 '10 at 13:41
  • 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 Mar 15 '10 at 15:19
  • 1
    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 Mar 15 '10 at 15:27
  • 2
    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 Mar 15 '10 at 16:05
6

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
  • 45,603
  • 7
  • 82
  • 122
6

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 for Ukraine
  • 12,283
  • 6
  • 56
  • 83
5

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

I came across this problem too and spent a whole day just to realize vector's address changed and the saved addresses became invalid. For my problem, my solution was that

  1. save raw data in the vector and get relative indices
  2. after the vector stopped growing, convert the indices to pointer addresses

I found the following works

  1. pointers[i]=indices[i]+(size_t)&vector[0];
  2. pointers[i]=&vector[ (size_t)indices[i] ];

However, I haven't figured out how to use vector.front() and I am not sure whether I should use pointers[i]=indices[i]*sizeof(vector)+(size_t)&vector[0] . I think the reference way(2) should be very safe.

path4
  • 91
  • 1
  • 1
2

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
  • 950
  • 9
  • 22
  • 2
    Invalidation will depend on the use of the pointers. If `int *ptr = &v[0];` 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 Mar 15 '10 at 13:43
  • 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 Mar 15 '10 at 13:47
1

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
  • 76,767
  • 18
  • 98
  • 146