在 std::launder 之前,所有 std::vector 的实现都是不可移植的吗?

Adr*_*ian 10 c++ placement-new language-lawyer c++11

当在实例emplace_back()上调用时std::vector,会在先前分配的存储中创建一个对象。这可以通过 Placement-new 轻松实现,它非常便携。但现在,我们需要访问嵌入的元素而不调用未定义的行为。

这篇文章中 我了解到有两种方法可以做到这一点

  1. 使用placement-new返回的指针: auto *elemPtr = new (bufferPtr) MyType();

  2. 或者,从 C++17 开始,std::launder所转换的指针bufferPtr
    auto *elemPtr2 = std::launder(reinterpret_cast<MyType*>(bufferPtr));

第二种方法可以很容易地推广到这样的情况,即我们有很多对象放置在相邻的内存位置,如std::vector. 但在 C++17 之前人们做了什么?一种解决方案是将placement-new 返回的指针存储在单独的动态数组中。虽然这当然是合法的,但我认为它并没有真正实现 std::vector [此外,单独存储我们已经知道的所有地址是一个疯狂的想法]。另一个解决方案是存储lastEmplacedElemPtrinside std::vector,并从中删除适当的整数 - 但由于我们实际上没有MyType对象数组,这可能也是未定义的。事实上,此 cppreference 页面中的一个示例声称,如果我们有两个相同类型的指针比较相等,并且其中一个可以安全地取消引用,则取消引用另一个可能仍然是未定义的。

那么,在 C++17 之前有没有办法以可移植的方式实现 std::vector 呢?或者也许 std::launder 确实是 C++ 的一个关键部分,当涉及到新的放置时,自 C++98 以来就缺失了?

我知道这个问题表面上与SO上的许多其他问题相似,但据我所知,他们都没有解释如何合法地迭代由placement-new构造的对象。事实上,这一切都有点令人困惑。例如,示例形式的cppreference 文档中的 std::aligned_storage注释 似乎表明 C++11 和 C++17 之间存在一些变化,并且简单的别名违规reinterpret_cast在 C++17 之前是合法的 [无需std::launder]。类似地,在文档 的示例中,std::malloc 他们只是对返回的指针进行指针算术std::mallocstatic_cast在正确的类型之后)。

相比之下,根据这个SO问题的答案 ,当涉及到placement-new和reinterpret_cast

自 C++11(特别是 [basic.life])以来,出现了一些重要的规则澄清。但规则背后的意图并没有改变。

F.v*_*.S. 1

P0593R6P1971R0/RU007之后的 IIUC都合并到 C++20 中,并且与先前修订版的缺陷报告相当可观,因此std::vector不需要可移植的实现std::launder

首先,在allocate给定分配器的函数(可能调用operator newstd::malloc或类似的函数)返回后,在分配的存储中隐式创建一个value_type[N]数组(其中N等于传递给 的请求数allocate)(感谢 P0593R6),因此指针算术有效。即使元素可能是未构造的。

其次,当我们使用不带的placement-new时std::launder,构造的对象可以被视为数组元素,因为即使元素类型具有 const/reference 非静态数据成员,新对象也会透明地替换数组元素(感谢 P1971R0/RU007 和后续)在P2103R0/US041中修复)。