开发者

Does std::vector change its address? How to avoid

开发者 https://www.devze.com 2022-12-22 19:30 出处:网络
Since vector elements are stored contiguously, I g开发者_开发问答uess it may not have the same address after some push_back\'s , because the initial allocated space could not suffice.

Since vector elements are stored contiguously, I g开发者_开发问答uess 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.


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.


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.


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.


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.


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.


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.


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消