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

Why do iterators need to be default-constructible

类别 forwardbidirectionalrandom access 的迭代器需要默认-可构造的。

这是为什么,为什么 inputoutput 运算符不必是默认可构造的?

需要前向迭代器和更强的迭代器来引用一些外部序列(参见 [forward.iterators]/6 说 "If a and b are both dereferenceable, then a == b if and only if *a and *b are bound to the same object.")

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

仅满足输入迭代器或输出迭代器要求(没有更强)的迭代器本身可能包含由 operator++ 修改的状态,因此该状态可能无法默认构造.没有仅在 input/output 迭代器上运行的算法需要默认构造它们,因此不需要。

  • 在此处找到 "no true scotsman" 参数 ;)

前向/双向/随机访问迭代器通常可以是指针 - 如果代码恰好是这种方式,那么如果构造和初始化可以离开本地化,那么从历史上看,它会帮助从使用指针的代码迁移到迭代器。强制进行更多的大规模更改会使许多试图将旧代码从显式使用指针迁移到迭代器的人感到沮丧。更改它现在会破坏很多代码。

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

reference for iterators

Input/Output 迭代器:

Input Iterator:一旦 InputIterator i 递增,其先前值的所有副本都可能失效。

Output Iterator:在此操作之后,r 不需要是可解引用的,并且 r 先前值的任何副本不再需要是可解引用或可递增的。

如果我们看一下,就会清楚这些迭代器被设计为尽可能以最简单的方法使用。就像数组索引或简单指针将用于单遍算法一样。 因此真的不需要默认构造函数。

但是请注意,仅仅因为在技术上不需要默认构造函数,并不意味着您可以根据需要实施它们。

Forward iterators:

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

有很多原因与历史编程原因等有关,我相信这在某种程度上都是正确的。在某种程度上,我认为委员会认为需要实现迭代器和 randomAccessIterator 默认构造之间的某个地方,而前向迭代器似乎是最佳选择。

但是有一个很好的理由:

前向迭代器支持多遍算法,因此要求迭代器的副本在迭代器 used/incremented 之后仍然有效。 如果这些副本仍然有效,则意味着该算法将允许我们在某个地方 "save" 它们。这也意味着我们保存它们的迭代器需要有一个 default/initial 值。

考虑:

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

根据定义,这是有效的,因为我们被允许将迭代器保存一段时间,因为要求该迭代器的副本将保持有效。 (至少直到迭代器指向的数据结构被销毁)

然而,要创建这个 class ForwardIterator 显然需要一个默认构造函数...