【问题标题】:Using std::map::extract to modify key使用 std::map::extract 修改密钥
【发布时间】:2018-11-07 19:15:06
【问题描述】:

我的实现使用 std::map 来存储数据。当我开始编写代码时,它似乎是最好的选择。现在我到了必须更改地图内所有对象的键值的地步。

问题是每个对象都指向地图内的另一个对象:

class AND : public node{
    vector <node*> inputs;
    vector <node*> outputs;
}

地图是这样声明的:

map<unsigned int, AND> all_ANDs;

我的问题是:如果我使用 C++17 中的 map::extract 来修改 all_ANDs 映射中的键值,我的指针(例如属性输入内的指针)是否会继续指向合适的地方?

换句话说:如果我用extract改变“first”元素的值,“second”的地址会保持不变?

我从this link 注意到字符串“papaya”保持不变(并且工作正常)。但我想确定指针。

【问题讨论】:

  • 这很明显是XY problem。您需要更改地图的键这一事实意味着您的设计存在缺陷。改变设计。另外:到目前为止,您尝试过什么来回答您的问题?为什么不简单地尝试extract 一个元素并用另一个键重新insert 它,然后检查它的地址?如果我们能做到,那么你也能做到!
  • 确保您的对象AND 不可移动和不可复制的一种方法。如果必须更改地址,编译器会呕吐。
  • @Walter “为什么不简单地尝试提取一个元素并用另一个键重新插入它,然后检查它的地址?”这是一个糟糕的建议,你不应该从特定实现的行为中推断出需求。
  • @Slave 这不是一个糟糕但实用的建议。你所做的是假设这样一个练习的结果可以用来推断需求。我从没想过。反之亦然:如果实验表明某个应用程序中的地址发生了变化,那么标准是否另有规定都无关紧要。
  • @Walter 但如果地址没有改变结果是无用的,这就是你的建议不好的原因,你应该依赖记录在案的行为

标签: c++ pointers extract stdmap


【解决方案1】:

是的 您在帖子中已经引用的reference 明确指出没有元素被复制或移动。 (这假设您的代码 sn-p 中的 node 不引用 map::node_type)。

同样适用于映射节点的insert操作(在修改其键之后):

如果插入成功,则在节点句柄中保存的元素的指针和引用无效,并且在提取之前获得的对该元素的指针和引用有效。 (C++17 起)

但是,访问extract()ion 和re-insert()ion 之间的对象具有未定义的行为,并且在提取状态下获取的地址使用有限。引用标准:

提取成员仅使删除元素的迭代器无效; 指向被移除元素的指针和引用仍然有效。然而, 通过这样的指针和引用访问元素,而 元素由 node_type 拥有是未定义的行为。参考文献和 指向由 node_type 拥有时获得的元素的指针是 如果元素插入成功则无效。

说明

本质上,map&lt;&gt; 被实现为节点树,每个节点都包含一个keyT(向用户公开为pair&lt;const Key, T&gt;)。节点(通常)在堆上分配,并且对象的地址与节点的地址相关。 map::extract() 从树中取消链接一个节点并返回一个 节点句柄(一个持有指向映射节点的指针的对象),但 AFAIK 节点本身不会被重新分配、移动或复制.在map::insert(handle) 上,节点根据其(新)键重新链接到树中。同样,这不涉及节点的重新分配、移动或复制。

备注

以上是粗略的草图。事情的实际完成方式可能更复杂,而且实现也有定义。正如herenode_handle 所解释的,确实允许通过成员函数更改密钥

Key &node_handle::key() const;

没有具体说明这是如何在幕后完成的,我推测该实现使用联合或一些强制转换来允许这样做。当然,地图必须向用户显示pair&lt;const Key,T&gt;,以防止他们更改键并因此破坏地图,但这对于从地图中提取的元素而言无关紧要。

【讨论】:

  • @Slava insert 手册说“在提取该元素之前获得的指向该元素的指针和引用变得有效”
  • "如果插入成功,则节点句柄中持有的元素的指针和引用无效,提取之前获得的指向该元素的指针和引用有效”对我来说似乎很清楚。 (编辑 - 被打败了 13 秒!)
  • 我错过了,但这需要成为答案的一部分,extract 保证在这种情况下显然是不够的。
  • 不,node 不是 map::node_type。它是一个用户定义的类。
  • node handle 公开了一个 non-const 键引用,以便您可以在它不在地图中时对其进行变异
【解决方案2】:

我的上述回答解决了您的直接问题。但是,正如我在评论中建议的那样,这似乎是XY problem。我怀疑的:

  1. 你有一些 AND 对象的结构,它们通过它们的 inputsoutputs 字段相互关联。任何重新分配都不能破坏此链接,因此您不能将它们存储在不断增长的vector&lt;AND&gt; 中并进行重新分配。

  2. 您还想根据一些key 对这些对象进行排序,因此将它们存储在map&lt;Key,AND&gt; 中,确实在增长时不会重新分配。

  3. 您现在想根据另一个键对它们进行排序(和/或更改所有键)。

(如果您实际上对排序不感兴趣,而只想通过键查找对象,则应该使用unordered_map 而不是map,它支持O(n) 中的find() 而不是@987654333 @ 操作。)

我建议您使用不同的数据布局:

  1. 您存储AND 对象的方式允许在不重新分配的情况下增加它们的数量。这里一个明显的选择是deque&lt;AND&gt;,因为

    双端队列两端的插入和删除永远不会失效 指向其余元素的指针或引用

    您还可以使AND 不可复制和不可移动,确保一旦分配它们的地址就永远不会改变(并且指向它们的指针在销毁之前保持有效)。

  2. 您可以通过实际处理指向存储对象的指针来支持任何按键查找或按键排序操作,方法是对pair&lt;key,AND*&gt; 的向量进行排序,或者使用map&lt;key,AND*&gt; 或@ 987654339@。您甚至可以同时为每个对象设置不同的键(以及每个对象的映射)。

  3. 当您必须重新设置所有对象的键时,只需忘记旧映射并创建一个新映射:由于映射只存储指针而不存储对象,这不会影响您的链接。

    李>

【讨论】:

  • 是的,沃尔特,你是完全正确的。考虑到我的实施的问题就像你描述的那样。虽然我需要他们订购。我在我的项目的另一个分支上使用的地图的发现做其他事情。我正在考虑更改为向量并将键值作为 AND 的属性并使用基于此属性的排序。
  • 基本上,我有 GRAPH 类,它在地图上保存 AND 对象。这个类图我用来读取一些文件,根据文件构造这些东西,然后对结构化数据进行一些计算。现在我开始使用类 SYNTHESIZER 创建自己的文件,该类继承自 GRAPH,并且在这种情况下,映射结构没有用处。我正在考虑在合成器类中删除继承并从映射更改为向量,正如您对 deque 所建议的那样,尽管我需要对 AND 进行排序来编写文件。
【解决方案3】:

您的 map 包含实际的 AND 对象,而不是指向对象的指针。因此,如果存储在您的vectors 中的AND* 指针指向mapAND 对象,那么一旦这些对象从@ 中擦除,这些指针将变为无效987654327@.

然而,提取只是将指定节点与map解除链接,该节点及其键和值在内存中仍然有效。可以将节点重新插入到map 中,而不影响节点的键和值的地址。在这方面,vectors 中的指针不会变得无效(尽管在节点与容器分离时取消引用它们是未定义的)。

另一种选择是将您的map 改为持有AND* 指针。或者更好的是,考虑在map 中使用std::shared_ptr&lt;AND&gt;,在vectors 中使用std::shared_ptr&lt;node&gt;,而不是原始指针。那么map 条目是否被擦除提取 无关紧要,只要有活动的shared_ptr 引用,AND 对象将保持有效他们。

【讨论】:

  • “是的,一旦这些对象因任何原因从地图中删除后,这些指针将变得无效”这不是extarct 文档所说的我相信
  • 这可能是一个更好的设计,但是 C++17 中添加的提取/节点句柄机制确实允许在不更改地址的情况下取消链接和重新链接树节点。
  • 一旦从地图中删除这些对象,这些指针将变得无效 这是错误通过map::extract删除
  • @Walter 实际上不是,至少在你再次发送map::insert 之前
  • @LightnessRacesinOrbit 你怎么知道?这毫无意义:对象本身的地址被重新存储,但在两者之间发生了变化???不,对象从未被移动/复制,只有句柄被移动/复制。
猜你喜欢
  • 1970-01-01
  • 2011-01-17
  • 2020-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多