§23.1.2.8声明对set / map的插入/删除操作不会使对这些对象的任何迭代器无效(除了指向已删除元素的迭代器)。
现在,请考虑以下情况:您希望实现具有唯一编号节点的图形,其中每个节点都具有固定数量(例如4个)的邻居。利用上述规则,你可以这样做:
class Node {
private:
// iterators to neighboring nodes
std::map<int, Node>::iterator neighbors[4];
friend class Graph;
};
class Graph {
private:
std::map<int, Node> nodes;
};
(编辑:由于第4行中Node
的不完整性(请参阅回复/评论),并非完全相同,但无论如何都是这样的。
这很好,因为这样你可以插入和删除节点而不会使结构的一致性失效(假设你跟踪删除并从每个节点的数组中删除删除的迭代器)。
但是,假设您还希望能够存储“无效”或“不存在”的邻居值。不用担心,我们可以使用nodes.end()
......或者我们可以吗?是否存在某种保证:{J} 1}在上午8点与nodes.end()
相同,在数十亿次插入/删除后?也就是说,我可以安全nodes.end()
将作为参数接收的迭代器与某些Graph方法中的==
进行比较吗?
如果没有,这会有效吗?
nodes.end()
也就是说,我可以在构造时将class Graph {
private:
std::map<int, Node> nodes;
std::map<int, Node>::iterator _INVALID;
public:
Graph() { _INVALID = nodes.end(); }
};
存储在变量中,然后在我想将邻居设置为无效状态时使用此变量,还是将其与方法中的参数进行比较?或者有可能在某个地方,指向现有对象的有效迭代器将比较等于nodes.end()
?
如果这也不起作用, 我可以为无效的邻居值留出空间吗?
答案 0 :(得分:5)
你写(我强调):
标准中的23.1.2.8声明对set / map的插入/删除操作不会使任何迭代器无效(指向已删除元素的迭代器除外)。
实际上,23.1.2 / 8的文本有点不同(再次,我强调):
插入成员不应影响迭代器的有效性并将引用到容器,并且擦除成员应仅使迭代器和对已擦除元素的引用无效。
我读到这个:如果你有一个地图,并以某种方式获得一个迭代器到这个地图(再次:它没有说到地图中的一个对象),这个迭代器将保持有效尽管插入和删除元素。假设std::map<K,V>::end()
获得“地图中的迭代器”,则不应通过插入/删除使其失效。
这当然留下了一个问题,即“未通过无效”意味着它总是具有相同的值。我个人的假设是没有具体说明。但是,为了使“无效”短语有意义,相同地图的std::map<K,V>::end()
的所有结果必须始终相等,即使面对插入/删除:
my_map_t::iterator old_end = my_map.end();
// wildly change my_map
assert( old_end == my_map.end() );
我的解释是,如果old_end
在对地图的更改(作为标准promisses)期间保持“有效”,那么该断言应该通过。
免责声明:我不是母语人士,并且非常很难消化那些可怕的神圣PDF版本。事实上,总的来说,我就像瘟疫一样避免它。
哦,我的第一个想法也是:学术POV的问题很有趣,但他为什么不简单地存储密钥而不是迭代器呢?
答案 1 :(得分:4)
23.1 / 7表示end()返回一个
的迭代器是容器的过去值。
首先,它确认end()
返回的是迭代器。其次,它说迭代器不指向特定元素。由于删除只能使指向某处的迭代器无效(对于要删除的元素),删除不能使end()
无效。
答案 2 :(得分:3)
嗯,只要比较和这样的工作,就没有什么能阻止特定的集合实现让end()
依赖于集合的实例和时间。这意味着,或许, end()
值可能会发生变化,但old_end == end()
比较仍应产生真实。 (编辑:虽然在阅读了j_random_hacker的评论后,我怀疑这一段本身的评估结果是 true ;-),而不是普遍的 - 请参阅下面的讨论)
我也怀疑你可以在std::map<int,Node>::iterator
类中使用Node
,因为类型不完整,但是(不确定)。
此外,由于您的节点是唯一编号的,因此您可以使用int
来键入它们并为无效保留一些值。
答案 3 :(得分:1)
假设(1)用红黑树(2)实现的地图,你在“多次插入/删除后”使用相同的实例 - 回答“是”。
相对的implmentation我可以告诉我所知道的stl的所有化身都使用树算法。
答案 4 :(得分:1)
几点:
1)end()引用一个超过容器末尾的元素。插入或删除更改容器时它不会更改,因为它没有指向元素。
2)我想也许你可以改变你在Node中存储4个迭代器数组的想法,以使整个问题更有意义。你想要的是向Graph对象添加一个新的迭代器类型,它能够迭代单个节点的邻居。这个迭代器的实现需要访问map的成员,这可能会让你走上使Graph类扩展map集合的道路。如果Graph类是扩展的std :: map,那么语言会发生变化,您不再需要存储无效的迭代器,而只需要编写算法来确定谁是地图中的“下一个邻居”。 / p>
答案 5 :(得分:1)
(多)集和(多)映射中的迭代器在插入和删除时不会失效,因此将.end()与之前存储的.end()进行比较将始终产生真实
以GNU libstdc ++实现为例,其中.end()在maps中返回默认的初始化值Rb_tree_node
来自stl_tree.h:
_M_initialize()
{
this->_M_header._M_color = _S_red;
this->_M_header._M_parent = 0;
this->_M_header._M_left = &this->_M_header;
this->_M_header._M_right = &this->_M_header;
}
答案 6 :(得分:1)
我认为很清楚:
end()返回一个遍及结尾的元素的迭代器。
插入/删除不影响现有的迭代器,因此返回的值始终有效(除非您尝试删除传递的元素然后结束(但这会导致未定义的行为)。
因此,当与使用operator ==的原始数据进行比较时,end()生成的任何新迭代器(将会有所不同)将返回true。
使用赋值运算符生成的任何中间值=都有一个post条件,它们与operator ==相等,而operator ==对于迭代器是传递的。
所以是的,存储end()返回的迭代器是有效的(但是由于带有关联容器的保证,因此对于vector等无效)。
请记住,迭代器不一定是指针。它可能是容器的设计者已经定义了类的所有操作的对象。
答案 7 :(得分:0)
我认为这完全取决于使用什么类型的迭代器。
在向量中,end()是超过结束指针的那个,当插入和删除元素时,它会明显改变。
在另一种容器中,end()迭代器可能是一个特殊值,如NULL或默认构造元素。在这种情况下,它不会改变,因为它没有指向任何东西。而不是像指针一样的东西,end()只是一个值来比较。
我相信set和map迭代器是第二种,但我不知道有什么要求以这种方式实现它们。
答案 8 :(得分:0)
C ++ Standard声明迭代器应该保持有效。它是。标准明确规定在23.1.2 / 8中:
插入成员不会影响 迭代器的有效性,并且引用容器,并且擦除成员只会使迭代器和引用无效擦除元素。
在21.1 / 7中:
end()返回迭代器,这是容器的过去值。
因此迭代器old_end
和new_end
将有效。这意味着我们可以获得--old_end
(称之为it1
)和--new_end
(称之为it2
)并且它将是结束值迭代器(来自定义的{{ {1}}返回),因为关联容器的迭代器是双向迭代器类别(根据23.1.2 / 6)并且根据{{1的定义操作(表75)。
现在end()
应该等于--r
,因为它给出了结束值,它只有一个(23.1.2 / 9)。然后从24.1.3开始: a == b暗示++ a == ++ b 的条件。 it1
和it2
将提供++it1
和++it2
迭代器(来自++ r操作表74的定义)。现在我们得到old_end
和new_end
相等。
答案 9 :(得分:0)
我最近有一个类似的问题,但我想知道调用end()
来检索迭代器以进行比较是否可能有竞争条件。
根据标准,如果两个迭代器都可以取消引用并&*a == &*b
,或者两个迭代器都不能被解除引用,则它们被视为等效。查找粗体语句需要一段时间,并且在这里非常相关
因为除非它指向的元素已被删除,否则std::map::iterator
无法失效,因此保证end
返回两个迭代器,无论它们是什么状态。获得,将永远相互比较为真。