Повреждает ли вставка элементов в вектор указатель на вектор? - PullRequest
5 голосов
/ 15 августа 2010

В программе для имитации логических элементов я переключился с использования массивов

node N[1000];

на векторы

vector<node> N;

И моя программа работала отлично до использования векторов, но теперь она выводит неверные результаты, поэтому я попытался отладить и обнаружил, что ошибка возникает здесь:

node* Simulator::FindNode(string h)
{
    int i;
    for(i = 0; i < NNodes; i++)
    {
        if (N[i].getname() == h)
        {
            return &N[i];
        }
    }

    node n ;
    N.push_back(n);
    N[NNodes].setname(h);
    NNodes++;
    return &N[NNodes-1]; //why?because of NNodes++  
}

// ...

node* inp1;
node* inp2;
node* out;
string NodeName;

inp_file >> NodeName;
inp1 = FindNode(NodeName);
s1 = inp1;

inp_file >> NodeName;
inp2 = FindNode(NodeName); //inp1 is destroyed here 

inp_file >> NodeName;
out = FindNode(NodeName); //inp2 and inp1 are destroyed here 

При первом вызове FindNode первый указатель inp1 указывает на правильное место, которое &N[0].

При повторном вызове FindNode 1-й указатель inp1 указывает на мусор, а второй указатель inp2 указывает на правильное место &N[1].

При третьем вызове FindNode первый и второй указатели (inp1, inp2) указывают на мусор!И третий указатель указывает на правильное место.

Почему это произошло?
Как работает вектор, когда я вставляю в него элементы, и какие указатели я должен использовать, чтобы указывать на векторы-элементы?

Ответы [ 6 ]

9 голосов
/ 15 августа 2010

Да, он может перераспределять весь буфер, делая все указатели на старое местоположение недействительными.

Вы можете ограничить это, предварительно распределив, но это на самом деле просто повышение производительности. Лучше использовать индексы вместо сырых указателей.

6 голосов
/ 15 августа 2010

Несколько вещей.

Во-первых, насколько я могу судить, NNodes просто отслеживает размер. Но у вас есть std::vector::size() для этого. Затем вы используете его, чтобы получить последний вставленный элемент, но вы можете просто использовать std::vector::back() для этого: return &N.back();.

Также ваш параметр передается по значению, когда его, вероятно, следует передавать по const-reference: const string& h. Это позволяет избежать ненужных копий, и в общем случае * вы должны передавать вещи по const-reference, а не по значению.

А это плохо:

node n;
N.push_back(n);
N[NNodes].setname(h);

node, вероятно, должен иметь конструктор, который принимает const string& и задает имя во время инициализации. Таким образом, у вас никогда не будет узла без имени, как в:

node n(h);
N.push_back(n);

Или более кратко:

N.push_back(node(h));

Намного лучше.

Во-вторых, да, vector может сделать недействительными указатели на элементы; а именно всякий раз, когда необходимо увеличить емкость вектора. Если вы можете, reserve() емкость заранее, чтобы избежать перераспределения. В вашем случае вы не можете, поэтому вы можете пойти двумя разными путями.

Первый маршрут - уровень косвенности . Вместо того, чтобы указывать прямо на вещи, поместите их индекс в массив. Обратите внимание, что хотя их адрес может измениться, их расположение в векторе не изменится. Вы бы Simulator::FindNode вернули size_t и вернули N.size() - 1. Добавьте члена типа node& GetNode(size_t index), который просто делает return N[index]; (будет проверка ошибок, если вы хотите). Теперь, когда вам нужен член, передайте индекс этому члену на GetNode, и вы получите ссылку на этот узел обратно.

Другой путь - поменять контейнер. Вы можете использовать deque, например. Это не имеет непрерывного хранения, но это очень похоже на vector. push_back и pop_back по-прежнему O (1), и он все еще имеет хорошую когерентность. (И, между прочим, deque обменивает непрерывное хранилище на способность к push_front и pop_front за O (1) раз)

Важно то, что deque будет не лишать законной силы указатели во время операции push или pop с любого конца. Он работает по типу гибридного векторного списка, где вы получаете куски памяти для элементов, связанных вместе. Измените базовое хранилище на deque (и не берите и не ставьте что-либо посередине), и вы можете просто указать на вещи.

Однако, насколько я могу судить, у вас ужасно неэффективная карта. Вы отображаете имена на узлы. Вы, вероятно, должны просто использовать std::map, который имеет точный интерфейс, который вы пытаетесь воссоздать. Вы даже можете указать на любой элемент на карте, который никогда не отменяет действия.

* Правило: передавайте по const-reference, если только тип не является примитивным (встроенным, как int, double и т. Д.), Если размер шрифта меньше sizeof(void*) или если вам потребуется его копия в любом случае.

То есть не делайте этого:

void foo(const std::string& s)
{
    std::string ss(s); // make a copy, use copy
}

Но сделайте это:

void foo(std::string s) // make a copy, use copy
{
}

4 голосов
/ 15 августа 2010

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

Если вы заранее знаете, сколько элементов у вас будет в векторе, вы можете использовать Reserve () метод для предварительного выделения пространства.

0 голосов
/ 15 августа 2010

Представьте, что вам нужно написать код на основе массива, чтобы он мог при необходимости изменить размер массива.

Представь, как бы ты это сделал.

Представьте, что случилось бы с устаревшими указателями.

Перепишите свой код, чтобы использовать индексы, а не указатели или чтобы гарантировать, что перераспределение не произойдет, в зависимости от ситуации.

0 голосов
/ 15 августа 2010

Да, вставки будут лишать законной силы старые указатели на векторные элементы при перераспределении. Если вы действительно хотите использовать стабильные указатели, вы можете переключиться с вектора на deque. Он предлагает интерфейс, очень похожий на векторный, и может расти без перераспределения и перемещает предыдущее содержимое, выделяя дополнительные фрагменты.

Цена, которую вы платите за использование deque вместо вектора, является еще одним уровнем косвенности при произвольном доступе. В зависимости от вашего использования, это может быть совершенно неактуально. Вы должны перебрать всю деку, если это необходимо, используя итераторы. Это будет такая же быстрая итерация по вектору.

Выигрыш: ноль перераспределений!

0 голосов
/ 15 августа 2010

Возвращение указателя на внутренний член STL, вероятно, не лучшая идея. Когда вы передаете объект в контейнер STL, вы в основном отказываетесь от него. Вы говорите STL, что он может перемещать его так, как считает нужным для выполнения обещаний, которые дает вам контейнер. Возвращение индекса, в котором расположен узел, - лучшая идея, как упомянул Стивен Судит.

Получив индекс, вы можете создать функцию, которая возвращает копию содержимого интересующего вас узла. Таким образом, вы также поддерживаете инкапсуляцию данных в контейнере STL, не позволяя никому другому изменять его содержимое.

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