简体   繁体   中英

pointer to vector at index Vs iterator

I have a vector< Object > myvec which I use in my code to hold a list of objects in memory. I keep a pointer to the current object in that vector in the "normal" C fashion by using

Object* pObj = &myvec[index];

This all works fine if... myvec doesn't grow big enough that it is moved around during a push_back at which time pObj becomes invalid - vectors guarantee data is sequential, hence they make no effort to keep the vector at the same memory location.

I can reserve enough space for myvec to prevent this, but I dnt' like that solution.

I could keep the index of the selected myvec position and when I need to use it just access it directly, but it's a costly modification to my code.

I'm wondering if iterators keep the their references intact as a vector is reallocated/moved and if so can I just replace

Object* pObj = &myvec[index];

by something like

vector<Object>::iterator = myvec.begin()+index;

What are the implication of this?

Is this doable?

What is the standard pattern to save pointers to vector positions?

Cheers

No... using an iterator you would have the same exact problem. If a vector reallocation is performed then all iterators are invalidated and using them is Undefined Behavior.

The only solution that is reallocation-resistant with an std::vector is using the integer index.

Using for example std::list things are different, but also the are different efficiency compromises, so it really depends on what you need to do.

Another option would be to create your own "smart index" class, that stores a reference to the vector and the index. This way you could keep just passing around one "pointer" (and you could implement pointer semantic for it) but the code wouldn't suffer from reallocation risks.

Iterators are (potentially) invalidated by anything that could resize the vector (eg, push_back).

You could, however, create your own iterator class that stored the vector and an index, which would be stable across operations that resized the vector:

#include <iterator>
#include <algorithm>
#include <iostream>
#include <vector>

namespace stable {

template <class T, class Dist=ptrdiff_t, class Ptr = T*, class Ref = T&>
class iterator : public std::iterator<std::random_access_iterator_tag, T, Dist, Ptr, Ref>
{
    T &container_;
    size_t index_;
public:
    iterator(T &container, size_t index) : container_(container), index_(index) {}

    iterator operator++() { ++index_; return *this; }
    iterator operator++(int) { iterator temp(*this); ++index_; return temp; }
    iterator operator--() { --index_; return *this; }
    iterator operator--(int) { stable_itertor temp(*this); --index_; return temp; }
    iterator operator+(Dist offset) { return iterator(container_, index_ + offset); }
    iterator operator-(Dist offset) { return iterator(container_, index_ - offset); }

    bool operator!=(iterator const &other) const { return index_ != other.index_; }
    bool operator==(iterator const &other) const { return index_ == other.index_; }
    bool operator<(iterator const &other) const { return index_ < other.index_; }
    bool operator>(iterator const &other) const { return index_ > other.index_; }

    typename T::value_type &operator *() { return container_[index_]; }
    typename T::value_type &operator[](size_t index) { return container_[index_ + index]; }
};

template <class T>
iterator<T> begin(T &container) { return iterator<T>(container, 0); }

template <class T>
iterator<T> end(T &container) { return iterator<T>(container, container.size()); }

}

#ifdef TEST
int main() { 

    std::vector<int> data;

    // add some data to the container:
    for (int i=0; i<10; i++)
        data.push_back(i);

    // get iterators to the beginning/end:
    stable::iterator<std::vector<int> > b = stable::begin(data);
    stable::iterator<std::vector<int> > e = stable::end(data);

    // add enough more data that the container will (probably) be resized:
    for (int i=10; i<10000; i++)
        data.push_back(i);

    // Use the previously-obtained iterators:
    std::copy(b, e, std::ostream_iterator<int>(std::cout, "\n"));

    // These iterators also support most pointer-like operations:
    std::cout << *(b+125) << "\n";
    std::cout << b[150] << "\n";

    return 0;
}
#endif

Since we can't embed this as a nested class inside of the container like a normal iterator class, this requires a slightly different syntax to declare/define an object of this type; instead of the usual std::vector<int>::iterator whatever; , we have to use stable::iterator<std::vector<int> > whatever; . Likewise, to obtain the beginning of a container, we use stable::begin(container) .

There is one point that may be a bit surprising (at least at first): when you obtain a stable::end(container) , that gets you the end of the container at that time . As shown in the test code above, if you later add more items to the container, the iterator your obtained previously is not adjusted to reflect the new end of the container -- it retains the position it had when you obtained it (ie, the position that was the end of the container at that time, but isn't any more).

No, iterators are invalidated after vector growth.

The way to get around this problem is to keep the index to the item, not a pointer or iterator to it. This is because the item stays at its index, even if the vector grows, assuming of course that you don't insert any items before it (thus changing its index).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM