可以使用placement-new和vector :: data()来替换向量中的元素吗?

jog*_*pan 17 c++ vector std placement-new c++11

有两个关于替换不可赋值的向量元素的问题:

对象不可分配的典型原因是其类定义包含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);
}
Run Code Online (Sandbox Code Playgroud)

下面是一个使用函数的例子.这在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;
}
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 14

这是非法的,因为3.8p7描述了使用析构函数调用和placement new来重新创建对象,它指定了对数据成员类型的限制:

3.8对象寿命[basic.life]

7 - 如果在对象的生命周期结束之后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建一个新对象,指向原始对象的指针[. ..]可用于操作新对象,如果:[...]
- 原始对象的类型[...]不包含任何类型为const限定的非静态数据成员或引用类型[...]

因此,由于你的对象包含一个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;`
}
Run Code Online (Sandbox Code Playgroud)