Существует два существующих вопроса о замене векторных элементов, которые нельзя назначить:
Типичная причина того, что объект не присваивается, состоит в том, что его определение класса включает const
члены и, следовательно, имеет свои operator=
удален.
std::vector
требует, чтобы его тип элемента был назначаемым. И действительно, по крайней мере, с помощью GCC, ни прямого назначения (vec[i] = x;
), ни сочетание erase()
а также insert()
заменить элемент работает, когда объект не может быть назначен.
Может ли функция, подобная следующей, которая использует vector::data()
, прямое уничтожение элемента и размещение нового с помощью конструктора копирования, будут использоваться для замены элемента, не вызывая неопределенного поведения?
template <typename T>
inline void replace(std::vector<T> &vec, const size_t pos, const T& src)
{
T *p = vec.data() + pos;
p->~T();
new (p) T(src);
}
Пример используемой функции приведен ниже. Это компилируется в GCC 4.7 и, кажется, работает.
struct A
{
const int _i;
A(const int &i):_i(i) {}
};
int main() {
std::vector<A> vec;
A c1(1);
A c2(2);
vec.push_back(c1);
std::cout << vec[0]._i << std::endl;
/* To replace the element in the vector
we cannot use this: */
//vec[0] = c2;
/* Nor this: */
//vec.erase(begin(vec));
//vec.insert(begin(vec),c2);
/* But this we can: */
replace(vec,0,c2);
std::cout << vec[0]._i << std::endl;
return 0;
}
Это недопустимо, потому что 3.8p7, который описывает использование вызова деструктора и размещение нового для воссоздания объекта на месте, определяет ограничения на типы элементов данных:
3.8 Время жизни объекта [basic.life]
7 — Если по истечении времени жизни объекта и до повторного использования или освобождения хранилища, в котором занятый объект, создается новый объект в месте хранения, которое занимал исходный объект, указатель, указывающий на исходный объект [. ..] Можно
использоваться для манипулирования новым объектом, если: […] — тип исходного объекта […] не содержит никаких нестатических элементов данных чей тип является константным или тип ссылки […]
Так как ваш объект содержит элемент данных const, после вызова и размещения деструктора новый вектор внутреннего data
указатель становится недействительным, когда используется для ссылки на первый элемент; Я думаю, что любое разумное чтение пришло бы к выводу, что то же самое относится и к другим элементам.
Основанием для этого является то, что оптимизатор имеет право предполагать, что элементы const и опорных данных соответственно не модифицированы или повторно установлены:
struct A { const int i; int &j; };
int foo() {
int x = 5;
std::vector<A> v{{4, x}};
bar(v); // opaque
return v[0].i + v[0].j; // optimised to `return 9;`
}
Ответ @ ecatmur правильный на момент написания статьи. В C ++ 17 мы теперь получаем std::launder
(РГ21 предложение P0137). Это было добавлено, чтобы сделать такие вещи, как std::optional
работать с const
члены среди других случаев. Пока вы помните launder
(то есть очистить) доступ к вашей памяти, тогда это теперь будет работать без вызова неопределенного поведения.