我在公司的关联容器的新(C ++ 17)拼接界面上做了一个闪电话题。我演示了std::set::extract
,然后被问到迭代器和指向提取元素的指针会发生什么。他们抓住了我的错误的脚,我无法回答这个问题,但在谈话结束后就查了一下。
[associative.reqmts] 21.2.6.10在当前标准草案中的内容如下:
extract
成员只对已删除元素的迭代器无效;指针和对已删除元素的引用仍然有效。但是,当元素由node_type
拥有时,通过这样的指针和引用访问元素是未定义的行为。如果元素成功插入,则在node_type
拥有的元素的引用和指针无效。
(提议P0083R3已经包含这个措辞)
现在强调的部分真的令我不安。我理解有效但不可解除引用的指针(nullptr
)或迭代器(end iterator)的概念。我找到了David Vandevoorde的"definition" of valid pointers,并了解到也有有效但不可解除指示的不是nullptr
。 (即指针通过现有对象)
有了这一切,我的心理模型如下:
set
管理其数据的内部树中删除。剩余的树可能需要重新平衡。返回的node_handle
获取孤立树节点的所有权。由于标准,在1)中检索的指针仍然有效,并且不能由extract
更改,因此这也支持这种心理模型。但是,对于这个模型,没有理由为什么解除引用指针会突然被定义。因此,使用col ++上的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>
专业化,那么使用地图中的std::pair<K, V>
就会产生未定义的行为。你不想限制实现它实现所有这些的“魔法”方式,所以你做任何会观察到“神奇”未定义行为的东西。