考虑以下固定大小的 vector 的简化和不完整实现:

template<typename T>
class Vec {
  T *start, *end;

public:
  T& operator[](ssize_t idx) { return start[idx]; }

  void pop() {
    end--;
    end->~T();
  }

  template<typename... U>
  void push(U... args) {
    new (end) T { std::forward<U>(args)... };
    end++;
  }
};

现在考虑以下T:
struct T {
  const int i;
};

以及以下用例:
Vec<T> v;
v.push(1);
std::cout << v[0].i;
v.pop();
v.push(2);
std::cout << v[0].i;

索引运算符使用start指针访问该对象。那时的对象已被pop销毁,另一个对象已被push(2)在其存储位置中创建。如果我正确阅读了有关std::launder的文档,则意味着下面一行中v[0]的行为是不确定的。

应该如何使用std::launder来更正此代码?每次使用新的展示位置时,我们都必须洗头吗? stdlib的当前实现似乎使用的代码与上面发布的代码相似。这些实现的行为是否未定义?

最佳答案

应该如何使用std::launder来更正此代码?每次使用新的刊登位置时,我们都必须洗头吗?

如果将placement new的返回值分配给launder(),则可以从P0532R0中避免调用end。除非 vector 为空,否则您无需更改起始指针,因为start当前指向的对象在您提供的代码中仍具有有效的生存期。

同一篇文章指出,除非对象生存期已结束并已被新对象替换,否则launder()是禁止操作的,因此,如果不需要使用launder(),则不会造成性能损失:



stdlib的当前实现似乎使用的代码类似于上面发布的代码。这些实现的行为是否未定义?

是的。 P0532R0也讨论了这个问题,其内容类似于问题注释中的讨论:vector不直接使用placement new,placement new调用的返回值丢失在对 vector 分配器的函数调用链中,以及在任何情况下新的事件放置是逐个元素使用的,因此构造内部 vector 机无论如何都不能使用返回值。 launder()似乎是此处打算使用的工具。但是,分配器指定的指针类型根本不需要是原始指针类型,并且launder()仅适用于原始指针。当前,某些类型的当前实现尚未定义。 launder()似乎不是解决基于分配器的容器的一般情况的适当机制。

关于c++ - std::launder如何影响容器?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40165022/

10-09 07:54