考虑以下固定大小向量的简化且不完整的实现:
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::清洗正确地,这意味着v[0]
下面的行中未定义。
应该如何使用 std::launder 来纠正此代码?每次使用新的放置位置时,我们都必须清洗开始和结束吗? stdlib 的当前实现似乎使用与上面发布的代码类似的代码。这些实现的行为是否未定义?
How is std::launder
应该用来纠正这个代码?每次使用新的放置位置时,我们都必须清洗开始和结束吗?
From P0532R0,你可以避免需要打电话launder()
如果placement new的返回值被赋值给end
。您不需要更改起始指针,除非向量为空,因为当前指向的对象start
使用您提供的代码仍然具有有效的生命周期。
同一篇论文表明launder()
是一个无操作,除非对象的生命周期已经结束并被新的对象替换,所以使用launder()
如果没有必要,不会造成性能损失:
[...] 的类型std::launder(this)
相当于理查德·史密斯指出的那样:记住launder(p)
是一个无操作,除非 p 指向一个生命周期已结束的对象,并且在同一存储中创建了一个新对象。
stdlib 的当前实现似乎使用与上面发布的代码类似的代码。这些实现的行为是否未定义?
Yes. P0532R0也讨论了这个问题,内容与问题评论中的讨论类似:vector
不直接使用placement new,placement new调用的返回值在向量分配器的函数调用链中丢失,并且在任何情况下placement new都是逐个元素使用的,因此构建内部向量机制无法使用返回值反正。launder()
似乎是这里要使用的工具。但是,分配器指定的指针类型根本不需要是原始指针类型,并且launder()
仅适用于原始指针。对于某些类型,当前的实现目前尚未定义;launder()
似乎不是解决基于分配器的容器的一般情况的适当机制。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)