【问题标题】:Were all implementations of std::vector non-portable before std::launder?在 std::launder 之前,std::vector 的所有实现都是不可移植的吗?
【发布时间】:2020-10-19 21:02:52
【问题描述】:

当在std::vector 实例上调用emplace_back() 时,会在先前分配的存储中创建一个对象。这可以通过完美便携的placement-new轻松实现。 但是现在,我们需要在不调用未定义行为的情况下访问 emplaced 元素。

来自this SO post 我了解到有两种方法可以做到这一点

  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 [此外,单独存储我们已经知道的所有地址是一个疯狂的想法]。 另一种解决方案是将lastEmplacedElemPtr 存储在std::vector 中,并从中删除一个适当的整数——但由于我们并没有真正的MyType 对象数组,这可能也是未定义的。事实上,this cppreference page 的一个例子声称如果我们有两个指针 具有比较相等的相同类型,其中一个可以安全地取消引用,而取消引用另一个可以仍然是未定义的。

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

我知道这个问题与 SO 上的许多其他问题表面上相似,但据我所知,他们都没有解释如何合法地迭代由placement-new 构造的对象。事实上,这有点令人困惑。例如示例中的 cmets 表格cppreference documentation ofstd::aligned_storage 似乎暗示 C++11 和 C++17 之间发生了一些变化,一个简单的 别名违反 reinterpret_cast 在 C++17 之前是合法的 [不需要 std::launder]。同样,在来自documentation ofstd::malloc 的示例中 他们只是对std::malloc 返回的指针进行指针运算(在static_cast 之后指向正确的类型)。

相比之下,根据this SO question关于placement-new和reinterpret_cast的回答:

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

【问题讨论】:

标签: c++ c++11 language-lawyer placement-new


【解决方案1】:

P0593R6P1971R0/RU007 之后的 IIUC,两者都合并到 C++20 中,并且作为针对先前版本的缺陷报告相当可观,可移植的 std::vector 实现不需要 std::launder

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

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

【讨论】:

  • 如果value_type 不满足隐式生命周期开始的要求怎么办? en.cppreference.com/w/cpp/language/lifetime
  • @HolyBlackCat 任何元素的生命周期都不会自动开始,因此需要放置 new 或 construct_at 调用才能开始其生命周期。 IIUC P0593 允许“一个完整对象的生命周期隐式开始,而其子对象没有”的情况。
  • 哦!所以数组的生命周期总是开始的,即使元素的生命周期没有?
  • 是的。这是分配器的allocate 函数所必需的。
猜你喜欢
  • 1970-01-01
  • 2017-07-03
  • 1970-01-01
  • 2017-09-24
  • 2013-11-12
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
相关资源
最近更新 更多