考虑以下固定大小的 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/