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;
索引运算符使用起始指针来访问该对象.此时的对象被pop破坏,另一个对象通过push(2)在其存储位置创建.如果我正确地阅读了关于std::launder的文档,这意味着以下行中v [0]的行为是未定义的.
如何使用std :: launder来纠正这个代码?每次使用新产品时,我们是否必须开始和结束流水? stdlib的当前实现似乎使用类似于上面发布的代码.这些实现的行为是否未定义?
解决方法
从P0532R0开始,您可以避免需要调用lawn(),如果将placement new的返回值分配给end.除非矢量为空,否则您不需要更改开始指针,因为由start指定的对象仍然具有您提供的代码的活动生命周期.
同一篇文章指出,除非对象生命周期已经结束并且已经被一个新的对象所替代,否则,洗牌()是无效的,所以如果没有必要,使用流水线不会导致性能损失:
[…] the type of
std::launder(this)
is equivalent to just this as Richard Smith pointed out: Remember thatlaunder(p)
is a no-op unless p points to an object whose lifetime has ended and where a new object has been created in the same storage.
stdlib的当前实现似乎使用类似于上面发布的代码.这些实现的行为是否未定义?
是. P0532R0还讨论了这个问题,内容类似于问题评论中的讨论:向量不直接使用布局新,放置新调用的返回值在向量的分配器的函数链中丢失,并且在任何事件布局新的元素使用元素,所以构建内部向量机器不能使用返回值.洗衣()似乎是这里打算使用的工具.然而,由allocator指定的指针类型根本不需要是一个原始指针类型,而launder()只适用于原始指针.目前的实施方式目前尚未定义为某些类型;流氓()似乎不是用于解决基于分配器的容器的通用情况的适当机制.