为什么比较“end()”迭代器合法?

时间:2010-04-28 09:42:15

标签: c++ stl pointers iterator

根据C ++标准(3.7.3.2/4)使用(不仅是解除引用,还包括复制,转换,还有其他),无效指针是未定义的行为(如果有疑问也会看到{ {3}})。现在,遍历STL容器的典型代码如下所示:

std::vector<int> toTraverse;
//populate the vector
for( std::vector<int>::iterator it = toTraverse.begin(); it != toTraverse.end(); ++it ) {
    //process( *it );
}

std::vector::end()是假设元素超出的最后一个元素的迭代器。那里没有元素,因此使用指针通过迭代器是未定义的行为。

现在!= end()如何运作?我的意思是为了进行比较,需要构造迭代器包装无效地址,然后必须在比较中使用该无效地址,这也是未定义的行为。这种比较是否合法?为什么?

8 个答案:

答案 0 :(得分:25)

end()的唯一要求是++(--end()) == end()end()可能只是迭代器所处的特殊状态。end()迭代器没有理由必须对应任何类型的指针。

此外,即使它是一个指针,比较两个指针也不需要任何类型的解引用。请考虑以下事项:

char[5] a = {'a', 'b', 'c', 'd', 'e'};
char* end = a+5;
for (char* it = a; it != a+5; ++it);

该代码可以正常工作,它会反映您的矢量代码。

答案 1 :(得分:10)

你是对的,无法使用无效的指针,但你错了指向一个超过数组中最后一个元素的元素的指针是无效的指针 - 它是有效的。

C标准第6.5.6.8节说它定义明确且有效:

  

...如果表达式P指向   数组对象的最后一个元素,   表达式(P)+1点过去了   数组对象的最后一个元素......

但无法取消引用:

  

...如果结果指出一个过去了   它是数组对象的最后一个元素   不得用作a的操作数   被评估的一元*运算符......

答案 2 :(得分:5)

结尾的一个不是无效值(常规数组或迭代器都没有)。你不能取消引用它,但它可以用于比较。

std::vector<X>::iterator it;

这是一个奇异的迭代器。您只能为其分配一个有效的迭代器。

std::vector<X>::iterator it = vec.end();

这是一个完全有效的迭代器。你不能取消引用它,但你可以用它进行比较并减少它(假设容器有足够的大小)。

答案 3 :(得分:3)

咦?没有规则说迭代器只需要使用指针来实现。

它可能有一个布尔标志,例如,当增量操作看到它通过有效数据的末尾时,它会被设置。

答案 4 :(得分:1)

标准库的容器的end()迭代器的实现是实现定义的,因此实现可以起到它知道要支持的平台的技巧。
如果你实现了自己的迭代器,你可以做任何你想做的事 - 只要它符合标准。例如,如果存储指针,则迭代器可以存储NULL指针以指示结束迭代器。或者它可以包含布尔标志或诸如此类的东西。

答案 5 :(得分:1)

简单。迭代器不是(必然)指针。

他们有一些相似之处(即你可以取消引用它们),但那就是它。

答案 6 :(得分:0)

除了已经说过的(迭代器不需要指针),我想指出你引用的规则

  

根据C ++标准(3.7.3.2/4)   使用(不仅是解除引用,而且   还复制,铸造,等等)   无效指针未定义   行为

无论如何,

不适用于end()迭代器。基本上,当你有一个数组时,所有指向其元素的指针,加上一个指针过去的结尾,加上一个指向数组开始之前的指针,都是有效的。这意味着:

int arr[5];
int *p=0;
p==arr+4; // OK
p==arr+5; // past-the-end, but OK
p==arr-1; // also OK
p==arr+123456; // not OK, according to your rule

答案 7 :(得分:0)

我在这里回答,因为其他答案现在已经过时了;尽管如此,他们对这个问题并不十分正确。

首先,C ++ 14改变了问题中提到的规则。通过无效指针值的间接或将无效指针值传递给释放函数仍未定义,但现在实现定义了其他操作,请参阅Documentation of "invalid pointer value" conversion in C++ implementations

其次,单词很重要。应用规则时,您无法绕过定义。这里的关键点是“无效”的定义。对于迭代器,这在[iterator.requirements]中定义。事实上,pointers are iterators即使对他们来说“无效”的含义也略有不同。指针规则将“无效”渲染为“不通过无效值进行间接”,这是迭代器“not dereferenceable”的特例;但是,“不可引用”是暗示迭代器“无效”。 “无效”明确定义为“may be singular”,而“单数”值定义为“与任何序列无关”(在“可解除引用”的定义的同一段中)。该段甚至明确定义了“过去的最终价值”。

从[iterator.requirements]中的标准文本中可以清楚地看出:

  • 过去的结果值不被认为是可解除引用的(至少是标准库),正如标准所述。
  • 可解除引用的值不是单数,因为它们与序列相关联。
  • 过去的结果值不是单数,因为它们与序列相关联。
  • 如果迭代器肯定不是单数(通过否定“无效迭代器”的定义),则它不是无效的。换句话说,如果迭代器与序列相关联,则它无效。

end()的值是一个过去的结束值,它在序列失效前与序列相关联。所以根据定义它实际上是有效的。即使对字面上的“无效”有误解,指针规则也不适用于此。

允许==对这些值进行比较的规则在input iterator requirements中,由其他类别的迭代器(正向,双向等)继承。更具体地说,有效的迭代器are required to be comparable in the domain of the iterator以这种方式(==)。此外,转发迭代器要求指定the domain is over the underlying sequence。容器要求指定iteratorconst_iterator成员类型in any iterator category meets forward iterator requirements。因此,==上的{strong> end()和同一容器上的迭代器需要明确定义。作为标准容器,vector<int>也符合要求。这就是整个故事。

第三,即使end()是一个指针值(这可能发生在vector实例的迭代器的优化实现中),问题中的规则仍然不适用。原因如上所述(以及其他一些答案):“无效”涉及*(间接直通),而不是比较。 One-past-end value is explicitly allowed to be compared in specified ways by the standard.另请注意,ISO C ++不是ISO C,它们也巧妙地不匹配(例如,<指针值不在同一数组中,未指定与未定义),尽管它们在此处具有相似的规则。