Подтвердить что ты не робот

С++ векторная ссылка

Я смущен тем, как С++ управляет объектами в векторе. Скажем, я делаю следующее:

vector<MyClass> myVector;
myVector.push_back(a);
myVector.push_back(b);
MyClass & c = myVector[1];
myVector.erase(myVector.begin());

Является ли ссылка c все еще действительной (или, еще лучше, гарантировано ли она действительной)? Если нет, я должен всегда делать копию из ссылки для обеспечения безопасности?

4b9b3361

Ответ 1

Является ли ссылка c все еще действительной (или, еще лучше, гарантировано ли она действительной)?

Теперь он находится в состоянии undefined.

Если нет, мне нужно всегда делать копию из ссылки для обеспечения безопасности?

Если есть возможность удалить или переместить (и т.д.) между вашим извлечением и использованием вами, то да, вам нужно сделать копию.

Ответ 2

В отличие от ссылок на Java или С# (которые больше похожи на указатели С++, чем ссылки С++), ссылки на С++ являются "тупыми" как указатели, что означает, что если вы получаете ссылку на объект, а затем вы перемещаете этот объект в память, ваша ссылка больше не действительна.

Является ли ссылка c все еще действительной (или, еще лучше, гарантирована ли она)?

В случае, когда вы описываете, стандартным векторам не гарантируется сохранение объектов, которые он содержит в одном и том же месте в памяти при изменении содержимого вектора (удаление элемента, изменение размера вектора и т.д.).

Это приведет к аннулированию как итераторов, так и указателей/ссылок на содержащийся объект.

Если нет, мне нужно всегда делать копию из ссылки для обеспечения безопасности?

Существует несколько способов продолжить "указывать" на нужные объекты, все из которых подразумевают уровень косвенности.

Копия полного/значения

Самый простой способ сделать полную копию MyClass:

vector<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
MyClass c = x[1] ;    // c is a full copy of b, not a reference to b
x.erase(x.begin()) ;

Используя правый контейнер

Вторым простейшим является использование std::list, специально разработанного для вставки и удаления элементов, и не будет изменять содержащиеся в нем объекты, а также не указывать на них указатели, ссылки или итераторы:

list<MyClass> x ;
x.push_back(a) ;
x.push_back(b) ;
list<MyClass> it = x.begin() ;
++it ;
MyClass & c = *it ;
x.erase(x.begin()) ;

Использование указателей (небезопасно)

Другим было бы сделать std::vector<MyClass *>, который будет содержать указатели на MyClass вместо MyClass объектов. Затем вы сможете сохранить указатель или ссылку на заостренный объект с немного другой записью (из-за дополнительной косвенности):

vector<MyClass *> x;
x.push_back(a);     // a being a MyClass *
x.push_back(b);     // b being a MyClass *
MyClass * c = x[1]; // c points to the same object as b
x.erase(x.begin()); // note that a will still need separate deallocation

Это небезопасно, потому что нет ясного (насколько это касается компилятора) владельца объектов a и b, то есть отсутствует четкая часть кода, ответственная за освобождение от них, когда они больше не нужны (это как утечки памяти происходят в C и С++)

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

Использование интеллектуальных указателей (безопаснее)

Что-то лучше будет использовать умные указатели. Например, используя С++ 11 (или boost) shared_ptr:

vector< shared_ptr<MyClass> > x;
x.push_back(a);               // a being a shared_ptr<MyClass>
x.push_back(b);               // b being a shared_ptr<MyClass>
shared_ptr<MyClass> c = x[1]; // c points to the same object as b
x.erase(x.begin());           // No deallocation problem

Теперь, если вы используете shared_ptr и ничего не знаете о weak_ptr, у вас есть проблема, поэтому вы должны закрыть этот пробел.

Использование интеллектуальных указателей 2 (безопаснее)

Другим решением было бы использовать С++ 11 unique_ptr, который является эксклюзивным владельцем заостренного объекта. Поэтому, если вы хотите иметь указатель или ссылку на объект-указатель, вам придется использовать необработанные указатели:

vector< unique_ptr<MyClass> > x;
x.push_back(a);               // a being a unique_ptr<MyClass>
x.push_back(b);               // b being a unique_ptr<MyClass>
MyClass * c = x[1].get();     // c points to the same object as b
x.erase(x.begin());           // No deallocation problem

Обратите внимание, что вектор является единственным владельцем объектов, в отличие от вышеприведенного случая с smart_ptr.

Заключение

Вы кодируете на С++, то есть вы должны выбрать правильный метод для своей проблемы.

Но сначала вы должны быть уверены, что понимаете уровень косвенности, добавленный указателями, какие указатели и какие ссылки на С++ (и почему они не являются ссылками на С#/Java).

Ответ 3

От ссылка для vector:

[5] Итераторы векторов недействительны при перераспределении памяти. Кроме того, вставка или удаление элемента в середине вектора аннулирует все итераторы, указывающие на элементы, следующие за точкой вставки или удаления. Из этого следует, что вы можете не допускать, чтобы векторные итераторы были недействительными, если вы используете reserve() для предопределения столько памяти, сколько будет использоваться вектором, и если все вставки и удаления находятся на конце вектора.

Следовательно, ваш итератор недействителен и после erase и не должен использоваться.

Ответ 4

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