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

Можно ли использовать новые-векторные и 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;
}
4b9b3361

Ответ 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.