为什么迭代器需要默认可构造

Why do iterators need to be default-constructible

提问人:sbi 提问时间:3/3/2015 更新时间:3/3/2015 访问量:3249

问:

向、双向和随机访问类别的迭代器需要是默认可构造的。

为什么会这样,为什么输入输出运算符不必是默认可构造的?

C++ STL 迭代器 语言-Lawyer 标准库

评论

3赞 Shoe 3/3/2015
可能是因为许多人习惯于在 for 循环之外编写,以避免超过建议的 78 个字符的行宽。如果你要使用,那么 for 循环就不会那么性感了: .std::vector<T>::iterator it;std::vector<T>::iterator it = container.begin();for (; it != container.end(); ++it)
2赞 alfC 6/13/2018
不幸的是,某些算法的实现方式需要默认可构造。原则上,这似乎是避免过早分配的合理要求,但在我看到的例子中,它可以很容易地避免。我最近尝试实现一个迭代器,但不幸的是,需要默认构造器增加了一个不必要的间接级别。所以我最终一直在为额外的间接水平付出代价,我认为这个要求是非常不幸的,因为它有一个令人讨厌的隐性成本。

答:

18赞 Jonathan Wakely 3/3/2015 #1

正向迭代器和更强的迭代器需要引用一些外部序列(参见 [forward.iterators]/6,其中说“如果 和 都是可取消引用的,那么当且仅当 和 绑定到同一个对象。aba == b*a*b

这意味着它们通常只是其他内容(例如,指向容器中的元素或节点的指针)的轻量级句柄,因此没有理由不要求它们可以默认构造(即使默认构造创建了一个单一的迭代器,在分配新值之前不能用于任何操作)。所有非病态*正向迭代器都可以支持默认构造,并且依赖它使某些算法更容易实现。

仅满足输入迭代器或输出迭代器要求(并且没有更强的)的迭代器可能包含由修改的状态,因此该状态可能无法默认构造。任何只对输入/输出迭代器进行操作的算法都不需要默认构造它们,因此不需要。operator++

  • 在这里发现“没有真正的苏格兰人”的论点;)

评论

8赞 Mark Ransom 3/3/2015
该标准通常不会仅仅因为没有理由不指定某些内容而指定某些内容;他们通常有一个很好的理由。
2赞 sbi 3/3/2015
对我来说,这似乎非常特定于 STL 容器。很容易想到一些自定义迭代器,它存储了比 a 更多的状态,并且该状态也不能无中生有地创建。为什么输入和输出迭代器不应该是单数的呢?(事实上,流迭代器是默认可构造的。std::vector<>::iterator
11赞 Jonathan Wakely 3/3/2015
至少在 libstdc++ 中(也可能在 SGI STL 中),并依赖于默认构造的正向迭代器。 default 构造一个双向迭代器。从理论上讲,它们可以变成冗余副本(无论如何,它们稍后都会被分配新的值)。std::searchstd::partition_pointstd::minmax_elementstd::rotate
3赞 Jonathan Wakely 3/3/2015
我怀疑它以相反的方式发生,即不是故意只要求它用于前进,更好的是,它是“不需要”输入/输出的。SGI STL 的作者,也许还有 Stepanov,都依赖于 DefaultConstructible 作为指针的泛化。在标准化过程中的某个时刻,有人意识到在输入或输出迭代器中运行的算法实际上不需要默认构造,因此放弃了对这些类别的要求。如果有人费心去做这项工作,也许它也可以被丢弃为前向和双向。
1赞 Hunter Kohler 3/11/2022
真正的苏格兰人只使用原始指针。
3赞 Tony Delroy 3/3/2015 #2

正向/双向/随机访问迭代器通常可以是指针 - 如果构造和初始化可以保持离域,那么从历史上看,如果代码碰巧是这样,它将有助于从使用指针的代码迁移到迭代器。强制进行更多的大规模更改会让很多人感到沮丧,他们试图将旧代码从显式使用指针迁移到迭代器上。现在更改它会破坏很多代码。

输入和输出运算符通常通过对基础流或其他 I/O 对象的引用最优雅地实现,并且必须在构造时初始化引用。当然,实现可能会被迫推迟这一点,并在内部使用指针,但这肯定会让一些人误入歧途——看起来太像“C”了——所以标准促进引用的使用也就不足为奇了。

评论

0赞 MSalters 3/3/2015
这更像是一个论据,为什么它们必须是可分配的。 是默认可构造的,并且是流结束标记。std::istream_iterator
0赞 Tony Delroy 3/3/2015
@MSalters:对默认构造有非常具体的用途(作为哨兵),因此它可以优先考虑它并接受所需的任何实现妥协,但标准并不要求所有输入/输出迭代器都是默认可构造的,仍然允许单个实现做出他们认为最优雅的选择。std::istream_iterator
0赞 alfC 5/12/2017
我认为这个答案指出了困境,DefaultConstructible 是一个很好的要求,但迫使你根据指针实现事物,即使不需要先验地涉及指针,并在引用方面惩罚更优雅的实现。
4赞 Heinrich du Toit 3/3/2015 #3

迭代器参考

输入/输出迭代器:

输入迭代器:一旦 InputIterator i 递增,其先前值的所有副本都可能失效。

输出迭代器:在此操作之后,r 不需要可取消引用,并且不再需要 r 的先前值的任何副本可取消引用或可递增。

如果我们看一下这一点,似乎很明显,这些迭代器被设计为以最简单的方法使用。就像数组索引或简单指针将用于单通道算法一样。 因此,实际上没有必要使用默认构造函数。

但是请注意,仅仅因为从技术上讲不需要默认构造函数,并不能取消您实现它们的资格(如果需要)。

正向迭代器:

这些是需要默认构造函数的第一级迭代器,但为什么呢?

有很多原因与历史编程原因有关,我相信这在某种程度上都是有效的。在某种程度上,我认为委员会认为需要实现迭代器和随机访问迭代器之间的默认构造,而转发迭代器似乎是最佳选择。

然而,有一个很好的理由:

正向迭代器支持多通道算法,因此要求迭代器的副本在使用/递增迭代器后仍然有效。 如果这些副本仍然有效,则意味着该算法将允许我们将它们“保存”在某个地方。这也意味着我们保存它们的迭代器需要具有默认值/初始值。

考虑:

class MyClass
{
 public:
  void myFunction(ForwardIterator &i)
  {
    //do some code here
    savedIter = i;
    //do some code here
  }
 private:
  ForwardIterator savedIter;
}

根据定义,这是有效的,因为我们被允许将迭代器保存一段时间,因为要求这个迭代器的副本将保持有效。(至少在迭代器指向的数据结构被破坏之前)

但是,要创建此类,ForwardIterator 显然需要一个默认构造函数......

评论

3赞 sbi 3/4/2015
你可以保存同样好的复制构造迭代器,不是吗?总而言之,这个答案有很多人挥手。
0赞 Heinrich du Toit 3/4/2015
您能否解释一下如何创建一个“占位符”,以优雅的方式在没有默认构造函数的情况下保存迭代器?另外,英语不是我的第一语言,所以我不是 100% 确定你指的是“挥手”的哪一部分,你能解释一下,这样我就可以尝试改进答案。
0赞 DaveFar 1/5/2019
根据我的经验,挥手经常发生在使用引号或“显然”之类的词的地方。
0赞 glaba 11/17/2023
你完全可以使用放置新。不需要默认构造。