【问题标题】:Can placement-new and vector::data() be used to replace elements in a vector?可以使用placement-new 和vector::data() 替换向量中的元素吗?
【发布时间】:2012-10-06 04:20:38
【问题描述】:

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

一个对象不可赋值的一个典型原因是它的类定义包括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;
}

【问题讨论】:

  • @jalf 关键是你根本不会调用赋值运算符。
  • 我见过的几乎所有 STL 实现(以及现在的标准库)都将这种技术用于序列管理的高天。您在这里使用的这个特定用法中唯一让我大吃一惊的部分是放置构造可能抛出的可能性,此时您现在在向量中有一个被破坏的元素,但向量不知道这一点并且会尝试在自己的毁灭中再次毁灭它。好问题,顺便说一句。
  • @WhozCraig:我同意,也许这个函数的条件是复制构造函数是 nothrow ? (is_nothrow_copy_constructible)
  • 因为值语义妨碍了。来自 Java 或其他“面向引用”的编程语言,人们期望能够将常量对象绑定到可变变量。只要在 X 类中有一个 const 成员,就不能再分配给 X 类型的变量(因为变量只不过是 C++ 中的命名对象),而这很少是人们想要的。
  • @jogojapan 编译器可以并且确实执行此要求。尝试使用g++ -D_GLIBCXX_CONCEPT_CHECKS 进行编译。 (有一次,它的意图是要求它无法编译。由于概念没有进入这个版本,所以推迟了这个,但我希望在标准的下一个版本中会出现这种情况。)

标签: c++ vector c++11 std placement-new


【解决方案1】:

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

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

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

因此,由于您的对象包含一个 const 数据成员,在析构函数调用和放置 new 之后,向量的内部 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;`
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

    猜你喜欢
    • 2018-05-13
    • 1970-01-01
    • 2016-09-18
    • 2013-03-25
    • 2016-08-16
    • 2020-07-20
    • 2018-06-06
    • 2022-01-01
    • 1970-01-01
    相关资源
    最近更新 更多