Является ли возвращение ссылки на элемент в растущем векторе плохой практикой?

У меня странная ошибка памяти (segfault). После некоторого обзора кода я обнаружил, что передаю указатель на self (this) на значение члена внутри класса, который сам находится внутри вектора и вот-вот изменит свой адрес. Затем (возможно) вектор растет, и только что переданный указатель становится недействительным.

Насколько я понимаю, std::vector должен перемещать свое содержимое в память, поскольку оно гарантированно продолжается, верно? Я думал, что смогу преодолеть это, передав ссылку на константу вместо указателя (передав *this). Но у меня такой же segfault. Я не понимаю, почему ссылки могут стать недействительными, как и указатели! Ситуацию можно обобщить так:

class Foo
{
public:
    Foo(): x(new Bar(*this)){}
    Foo(Foo&&);
    Foo& operator=(Foo&&)
private:
    // some heavy to move data
    Bar* x;
};

class Bar
{
public:
    Bar(const Foo& foo): instance(foo){}
private:
    const Foo& instance;
};

std::vector<Zoo> vec;
// Do some stuff on vec, then x->instance become invalid. Why?

person sorush-r    schedule 31.05.2014    source источник
comment
"Но у меня такой же segfault". Это явно лишает законной силы хорошую практику!   -  person πάντα ῥεῖ    schedule 31.05.2014
comment
Где объявлено Zoo и что именно вы делаете с vector<Zoo>? Да, код, возвращающий элемент в растущем векторе по ссылке, может взорваться при первом перераспределении внутренней памяти, происходящем в векторе (по умолчанию при размерах 0, 1, 2, 4 и т. д.).   -  person bobah    schedule 31.05.2014
comment
Вы потенциально можете справиться с этим, зарезервировав достаточно места в векторе, чтобы он никогда не увеличивался, но в целом то, что вы описали, представляет собой бомбу замедленного действия, ожидающую взрыва.   -  person Retired Ninja    schedule 31.05.2014
comment
Является ли возвращение ссылки на элемент в растущем векторе плохой практикой? Только если вы сохраняете эту ссылку для будущего использования. Однако ничто в вашем опубликованном коде не говорит, где и как вы это делаете.   -  person R Sahu    schedule 31.05.2014
comment
@RetiredNinja Резервировать достаточно места для меня не вариант. Я собираюсь использовать std::list (с линейным временем доступа!)   -  person sorush-r    schedule 31.05.2014
comment
@RSahu Да, я сохраняю ссылку на будущее. На самом деле после того, как синтаксические анализаторы создали структуры данных, алгоритм должен работать с ними.   -  person sorush-r    schedule 31.05.2014


Ответы (1)


Когда вектору необходимо перераспределить свое хранилище, объекты, находившиеся в «старом» месте, удаляются после того, как они были скопированы (или перемещены) в новое хранилище. Перемещение не «телепортирует» объект в новое место, оно по-прежнему создает новый объект в новом месте.

Ссылка, которую вы держите, ссылается на «старый» объект, который был удален операцией векторного перераспределения. Использование этой ссылки приведет к неопределенному поведению. То же, что и в случае с указателем. Вы также не можете хранить итератор, он тоже недействителен.

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

person Mat    schedule 31.05.2014