Можно ли использовать новые-векторные и vector:: data() для замены элементов в векторе?
Есть два существующих вопроса об замене векторных элементов, которые не могут быть назначены:
Типичная причина того, что объект не может быть присвоен, состоит в том, что его определение класса включает в себя const
членов и, следовательно, имеет свой operator=
.
std::vector
требует, чтобы его тип элемента был назначен. И действительно, по крайней мере, используя GCC, ни прямое присвоение (vec[i] = x;
), ни комбинация erase()
и insert()
, чтобы заменить элемент, работают, когда объект не может быть назначен.
Может ли функция, подобная следующей, которая использует vector::data()
, прямое уничтожение элементов и размещение нового с помощью конструктора копирования, используется для замены элемента без возникновения поведения undefined?
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;
}
Ответы
Ответ 1
Это незаконно, потому что 3.8p7, который описывает использование вызова деструктора и нового места для воссоздания объекта на месте, указывает ограничения на типы членов данных:
3.8 Время жизни объекта [basic.life]
7 - Если после того, как срок жизни объекта закончился и перед хранилищем, которое объект занят, повторно используется или выпущен, создается новый объект в месте хранения, в котором был загружен исходный объект, указатель, указывающий на оригинал объект [...] может использовать для управления новым объектом, если: [...]
- тип исходного объекта [...] не содержит нестатического элемента данных , тип которого является const-квалифицированным или ссылочным типом [...]
Так как ваш объект содержит элемент данных const, после вызова деструктора и размещения новый векторный внутренний указатель data
становится недействительным, когда используется для обращения к первому элементу; Я думаю, что любое разумное чтение сделает вывод, что то же самое относится и к другим элементам.
Обоснование этого заключается в том, что оптимизатор имеет право предположить, что члены константы и ссылочные данные не изменяются или не изменяются соответственно:
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;`
}
Ответ 2
Ответ @ecatmur верен с момента его написания. В С++ 17 мы получаем std::launder
(предложение wg21 P0137). Это было добавлено, чтобы сделать такие вещи, как std::optional
, работать с членами const
среди других случаев. До тех пор, пока вы не запомните launder
(т.е. Очистите) ваши обращения к памяти, тогда это будет работать без вызова поведения undefined.