【发布时间】:2020-10-19 21:02:52
【问题描述】:
当在std::vector 实例上调用emplace_back() 时,会在先前分配的存储中创建一个对象。这可以通过完美便携的placement-new轻松实现。
但是现在,我们需要在不调用未定义行为的情况下访问 emplaced 元素。
来自this SO post 我了解到有两种方法可以做到这一点
-
使用placement-new返回的指针:
auto *elemPtr = new (bufferPtr) MyType(); -
或者,从 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])。但规则背后的意图并没有 改变了。
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: c++ c++11 language-lawyer placement-new