Меняет ли std :: vector свой адрес? Как избежать - PullRequest
9 голосов
/ 15 марта 2010

Поскольку векторные элементы хранятся смежно, я думаю, что после некоторых push_back-адресов он может не иметь один и тот же адрес, потому что начального выделенного пространства может не хватить.

Я работаю над кодом, где мне нужна ссылка на элемент в векторе, например:

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

Но это не обязательно так, что ptr содержит ссылку на v[0], верно? Как можно было бы это гарантировать?

Моей первой идеей было бы использование вектора указателей и динамического выделения. Мне интересно, есть ли более простой способ сделать это?

PS: На самом деле я использую вектор класса вместо int, но я думаю, что проблемы одинаковы.

Ответы [ 8 ]

12 голосов
/ 15 марта 2010

Не используйте резерв, чтобы отложить эту ошибку с висящим указателем - как тот, кто получил эту же проблему, пожал плечами, зарезервировал 1000, а затем несколько месяцев спустя потратил целую вечность, пытаясь выяснить какую-то странную ошибку памяти емкость вектора превысила 1000), я могу вам сказать, что это не надежное решение.

Вы хотите избегать взятия адреса элементов в векторе , если это вообще возможно, именно из-за непредсказуемого характера перераспределений. Если вам нужно, используйте итераторы вместо необработанных адресов, поскольку проверенные реализации STL сообщат вам, когда они станут недействительными, вместо случайного сбоя.

Лучшее решение - поменять контейнер:

  • Вы можете использовать std :: list - он не делает недействительными существующие итераторы при добавлении элементов, и только итератор для стертого элемента становится недействительным при удалении
  • Если вы используете C ++ 0x, std :: vector > является интересным решением
  • В качестве альтернативы, использование указателей и new / delete не так уж и плохо - просто не забудьте удалить указатели перед их стиранием . Это не трудно сделать правильно, но вы должны быть очень осторожны, чтобы не вызвать утечку памяти, забыв удалить. (Марк Рэнсом также указывает: это не исключительная ситуация, все содержимое вектора просачивается, если исключение вызывает уничтожение вектора.)
  • Обратите внимание, что ptr_vector boost не может безопасно использоваться с некоторыми из алгоритмов STL, что может быть проблемой для вас.
10 голосов
/ 15 марта 2010

Вы можете увеличить емкость базового массива, используемого вектором, вызвав reserve функцию-член:

v.reserve(100);

Пока вы не поместите более 100 элементов в вектор, ptr будет указывать на первый элемент.

6 голосов
/ 15 марта 2010

Как можно было бы это гарантировать?

std::vector<T> гарантированно будет непрерывным, но реализация может перераспределять или освобождать память для операций, изменяющих векторное содержимое (векторные итераторы, указатели или ссылки на элементы также становятся неопределенными).

Вы можете достичь желаемого результата, позвонив по номеру reserve. IIRC, стандарт гарантирует, что перераспределения не выполняются, пока размер вектора не превысит его зарезервированную емкость.

Как правило, я буду осторожен с этим (вы можете быстро попасть в ловушку ...). Лучше не полагаться на std::vector<>::reserve и постоянство итераторов, если вам не нужно.

5 голосов
/ 15 марта 2010

Еще одна возможность - это специально созданный умный указатель, который вместо сохранения адреса будет хранить адрес самого вектора вместе с индексом интересующего вас элемента. Затем он соберет их вместе и получит адрес элемента только тогда, когда вы разыменовываете его, что-то вроде этого:

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]; }
};

Тогда ваш int *ptr=&v[0]; будет заменен чем-то вроде: vec_ptr<int> ptr(v,0);

Пара моментов: во-первых, если вы переставляете элементы в вашем векторе между моментом создания «указателя» и разыменованием его, он больше не будет ссылаться на исходный элемент, а на какой-либо элемент бывает в указанной позиции. Во-вторых, это не проверка диапазона, поэтому (например) попытка использовать элемент 100 th в векторе, который содержит только 50 элементов, приведет к неопределенному поведению.

2 голосов
/ 15 марта 2010

Если вам не нужно хранить ваши значения непрерывно, вы можете использовать std :: deque вместо std :: vector. Он не перераспределяет, но содержит элементы в нескольких фрагментах памяти.

2 голосов
/ 15 марта 2010

Как заявили Джеймс МакНеллис и Александр Гесслер, reserve - это хороший способ предварительного выделения памяти. Однако для полноты картины я хотел бы добавить, что для того, чтобы указатели оставались действительными, все операции вставки / удаления должны выполняться из хвоста вектора, в противном случае смещение элементов снова сделает недействительными ваши указатели.

1 голос
/ 11 февраля 2014

Я тоже сталкивался с этой проблемой и потратил целый день, чтобы понять, что адрес вектора изменился, а сохраненные адреса стали недействительными. Для моей проблемы, мое решение состояло в том, что

  1. сохранить исходные данные в векторе и получить относительные индексы
  2. после того, как вектор перестал расти, преобразуйте индексы в адреса указателя

Я нашел следующие работы

  1. указатели [I] = индексы [I] + (size_t) и вектор [0];
  2. указатели [i] = & индексы вектора [(size_t) [i]];

Однако я не понял, как использовать vector.front (), и я не уверен, стоит ли мне использовать указатели [i] = индексы [i] * sizeof (вектор) + (size_t) & вектор [0]. Я думаю, что эталонный путь (2) должен быть очень безопасным.

1 голос
/ 15 марта 2010

В зависимости от ваших требований и варианта использования вы можете взглянуть на Boost's Pointer Container Library .

В вашем случае вы можете использовать boost::ptr_vector<yourClass>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...