std :: карта с указателями: неправильная оценка значения

0

Я пытаюсь сохранить в STL-карту указатель на объект Block:

void IO::parseInput(void)
{
    map<string, Block*> blocksMap;

    //=== Create new block and write it to the vector in database ===
    Block tmpBlock (bIndex, bName, bWidth, bHeight);
    Block* tmpPointer = db.addBlock(tmpBlock); //addBlock returns pointer to the added Block

    tmpPointer->printParams(); // -- here is correct output

    blocksMap.insert ( pair<string, Block*>(bName, tmpPointer) );

    // === Test hash ===
    blocksMap.find("anyLegalStringKey")->second->printParams(); // -- wrong output
}

addBlock в классе DB:

Block* DB::addBlock (Block& newBlock)
{
    blocks.push_back(newBlock);
    Block* ptrToLast = &blocks.back();
    return ptrToLast;
}

Вектор блока в классе DB:

class DB:
{
    private:
    //=== Database ===
    vector <Block> blocks;
};

Проблема: перед записью на карту я обращаюсь к объекту (который я хочу сохранить) с помощью указателя tmpPointer и распечатываю все его параметры. Это работает правильно. Затем я сохраняю этот указатель на карте с определенным ключом. Когда я пытаюсь получить доступ к тому же указателю, используя find в карте с определенным ключом, я получаю неправильный вывод (я также распечатываю все параметры).

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

  1. Все в порядке, и я получаю нормальный выход (в моем случае я распечатываю все параметры)
  2. Я получаю неправильные параметры (например, вместо индекса 7 я получил 21814704)
  3. Непрерывный вывод
  4. Ошибка сегментации

Интересно, что для одного и того же объекта блока у меня всегда есть одна и та же реакция (например, блок с именем "g22i" всегда не читается).

Векторные "блоки" в db содержат правильную информацию до и после сохранения указателей на карте.

Благодарю!

  • 2
    Когда вы вставляете push_back в вектор, вектор может расти, что требует перераспределения. Это делает недействительными ссылки и итераторы к его элементам.
  • 0
    Вы всегда должны проверять результат .find чтобы убедиться, что .find() != blocksMap.end() . Разыменование .end() - неопределенное поведение, и если оно ничего не нашло, вы разыменовываете, как только вы вызываете ->second
Показать ещё 2 комментария
Теги:
pointers
memory
reference
map

1 ответ

4
Лучший ответ

Вы пытаетесь использовать долговечные указатели для std::vector элементов. Это рецепт катастрофы. Все такие указатели будут аннулированы в тот самый момент, когда ваш вектор решает перераспределить себя.

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

Вы также можете использовать контейнер, который никогда не делает недействительными указатели на его элементы, такие как std::list. Но std::list не поддерживает произвольный доступ. Вы можете использовать std::deque, который поддерживает произвольный доступ (хотя и менее эффективно, чем std::vector) и сохраняет правильность указателей элементов, если вы не вставляете ничего в середину последовательности.

Наконец, вы можете использовать std::vector, но не забудьте сохранить [умные] указатели, чтобы Block объекты в нем, а не сами объекты Block. Идея состоит в том, чтобы гарантировать, что перераспределение векторов не приведет к перераспределению фактических объектов Block.

PS Еще одно замечание: когда вы объявляете map<string, Block*> blocksMap, элементы такой карты имеют тип pair<const string, Block*> (обратите внимание на дополнительную const). Т.е. map<string, Block*>::value_type фактически является pair<const string, Block*>.

Позже вы пытаетесь вызвать blocksMap.insert с blocksMap.insert pair<string, Block*>, в то время как blocksMap.insert фактически ожидает pair<const string, Block*>. Это компилируется, но подразумевает неявное преобразование из вашей pair<string, Block*> в pair<const string, Block*> выполняемую конструктором преобразования std::pair. Это не оптимально. Вот почему лучшей идеей может быть использование map<string, Block*>::value_type вместо того, чтобы пытаться записать тип элемента вручную

blocksMap.insert ( map<string, Block*>::value_type(bName, tmpPointer) );

Ещё вопросы

Сообщество Overcoder
Наверх
Меню