【问题标题】:How STL ordered containers know their end?STL 有序容器如何知道它们的结束?
【发布时间】:2016-02-12 19:26:28
【问题描述】:

我知道该标准并未规定 STL 容器必须以何种方式实现,而是为每个容器规定了一组要求。

然而,众所周知,STL 有序容器通常实现为red–black trees

您可以使用 std::setstd::map 各自的迭代器来迭代它们的元素,或者从 C++11 开始使用范围循环。

然而,令我困惑的是,STL 中的有序容器如何“知道” 它的“结束”。或者换一种说法,因为它们被实现为树, 容器的末端是如何实现的或可能是 实施了吗?

我知道标准规定了§23.2.1/c 一般容器要求Emphasis Mine):

begin() 返回一个迭代器,指向第一个元素 容器。 end() 返回一个迭代器,它是过去的值 对于容器。如果容器是空的,那么 begin() == end();

好的,对于连续容器来说,这很容易,但是如何为树实现这种“过去的”?

【问题讨论】:

  • 意识到“过去”是一个逻辑概念,而不是物理概念。它的字面意思是当您前进到最后一个有效条目时获得的迭代器,但该迭代器的内部内容可以是任何东西。
  • next() 和 end() 的自然概念来自终止广度优先和深度优先等树遍历,因此各个迭代器在概念上没有什么特别之处。
  • @MarkRansom 很公平,这是一个合乎逻辑的概念。但是你是如何实现这样一个逻辑概念的呢?我的意思是在代码中。
  • 我想我的意思是它完全是任意的。根据树的具体实现,可能会有一种自然而明显的方法,但我怀疑你会发现与标准库的实现一样多的不同方法。

标签: c++ c++11 stl stdmap stdset


【解决方案1】:

我刚刚检查了 Visual Studio 2013 STL 中 map 容器的实现,下面是 end 的实现方式。构造map时,分配RB树的头元素,并声明该元素为容器的末端。

当您通过有效的迭代器遍历容器时,operator++operator-- 只需跳过 head 元素。当你到达树的最后一个元素并增加一个迭代器时,它会向上爬(寻找右子树)并最终到达树的头部,即end

【讨论】:

    【解决方案2】:

    所有像这样的“类似列表”的容器都需要有某种哨兵节点作为结束,因为用户可以得到end(),在容器中插入一些东西,递减迭代器,递减的@987654322 @ 必须指向那个插入的元素。我的理解是有的实现会为此动态分配,有的会把动态的哨兵节点放到容器本身里面。

    【讨论】:

    • 我无法在标准中找到暗示 std::map::end()std::set::end() 必须返回可递减迭代器的位置。你所说的对于basic_stringarraydequelistvector 是正确的,但是 OP 询问了setmap
    • @АндрейБеньковский: N4527 23.2.1 [container.requirements.general]/12:除非另有说明,否则 [...] 调用容器成员函数 [...] 不应使迭代器无效,或更改该容器内对象的值。 -- 并且 map::insert 没有明确说明。 23.2.4 [associative.reqmts]/9:insert 和 emplace 成员不应影响迭代器和对容器的引用的有效性,而擦除成员应仅使迭代器和对被擦除元素的引用无效。
    • @АндрейБеньковский:特别是,对于basic_stringdequevector,它为真——在这些情况下,如果重新分配由插入迭代器和引用无效。而且你不能插入array,所以这在这里并不重要。
    猜你喜欢
    • 1970-01-01
    • 2011-06-07
    • 2012-06-04
    • 2019-11-07
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    相关资源
    最近更新 更多