我在我的公司做了一个关于关联容器新的(C++17)拼接接口(interface)的闪电演讲。我演示了 std::set::extract
然后被问到迭代器和指向提取元素的指针会发生什么。他们抓错了我的脚,我无法回答这个问题,但在谈话结束后立即查了一下。
[associative.reqmts] 21.2.6.10在目前的标准草案中是这样写的:
The
extract
members invalidate only iterators to the removed element; pointers and references to the removed element remain valid. However, accessing the element through such pointers and references while the element is owned by anode_type
is undefined behavior. References and pointers to an element obtained while it is owned by anode_type
are invalidated if the element is successfully inserted.
(提案P0083R3已经包含了这个字眼)
现在强调的部分真的让我很不安。我了解有效但不可取消引用的指针 (nullptr
) 或迭代器(结束迭代器)的概念。我找到了一个 "definition" of valid pointers通过 David Vandevoorde 了解到还有一些有效但不可取消引用的指针不是 nullptr
。 (即指向现有对象的指针)
有了这一切,我对发生的事情的心智模型如下:
- 检索到指向集合中某个元素的指针,因此该指针是有效的。取消引用该指针已定义并产生对集合中元素的访问。
- 现在从集合中提取相同的元素。从概念上讲,元素不会被复制、移动或以其他方式更改,它的关联节点只是从
set
管理其数据的内部树中删除。剩余的树可能需要重新平衡。返回的node_handle
拥有孤立树节点的所有权。
按照标准,在 1) 中检索到的指针仍然有效并且不能被 extract
更改,因此这也支持这种心智模型。然而,对于这个模型,没有理由取消引用指针会突然未定义。因此在 coliru 上使用 g++ seems to work as I would have expected . (这不是任何形式的证据)
标准为库实现者提供的余地似乎不必要地大。我错过了什么?我只看到在提取它们时设置值的常量性被丢弃,但看不到这会有什么影响。
同样的推理适用于最后引用的句子中提到的插入案例。
最佳答案
您的心智模型忽略了这样一个事实,即删除常量性,严格来说,必须是实现定义的。
node_handle
必须取得一个不同对象的所有权,但通过一些实现定义的魔法,该可变对象在没有被构造的情况下突然出现,具有与原始 const 对象相同的值和存储。
类似地,当它被插入到一个具有兼容分配器的集合中时,该集合会转换 node_handle
拥有的可变对象。回到原来的 const 对象。
这是未定义的行为,因为 const 对象已不复存在,而“它”属于 node_handle
。 , 但当它被重新插入时它又开始存在。
使用 node_handle
是未定义行为的同一种推理。如果您具有 std::pair<const K, V>
的用户定义特化,则来自 map 或 std::pair<K, V>
.您不想将实现限制在实现所有这些的“魔术”方式上,因此您可以制作任何可以观察到“魔术”未定义行为的东西。
关于c++ - 为什么取消引用指向 std::set 的提取节点的指针是未定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54925496/