18

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

一个对象不可赋值的一个典型原因是它的类定义包括const成员,因此它operator=被删除了。

std::vector要求其元素类型是可分配的。事实上,至少使用 GCC,当对象不可赋值时,直接赋值 ( vec[i] = x;)erase()insert()替换元素的组合都不起作用。

像下面这样使用vector::data()复制构造函数的直接元素销毁和放置 new 的函数是否可以用于替换元素而不会导致未定义的行为?

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;
}
4

2 回答 2

16

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

3.8 对象生命周期 [basic.life]

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

因此,由于您的对象包含一个 const 数据成员,在析构函数调用和放置 new 之后,向量的内部data指针在用于引用第一个元素时变得无效;我认为任何明智的阅读都会得出结论,这同样适用于其他元素。

这样做的理由是优化器有权假设 const 和 reference 数据成员没有分别修改或重新安装:

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;`
}
于 2012-10-16T09:42:12.107 回答
4

@ecatmur 的答案在撰写本文时是正确的。在 C++17 中,我们现在得到std::launderwg21 提案 P0137)。添加此功能是为了在其他情况下std::optional与成员一起工作。const只要您记得launder(即清理)您的内存访问,那么现在这将工作而不会调用未定义的行为。

于 2017-10-30T13:16:22.487 回答