对于向量,为什么更喜欢迭代器而不是指针?

时间:2015-09-21 18:03:12

标签: c++ pointers vector iterator gotw

在Herb Sutter的When Is a Container Not a Container?中,他展示了一个将指针放入容器的例子:

  // Example 1: Is this code valid? safe? good?
  //
  vector<char> v;

  // ...

  char* p = &v[0];

  // ... do something with *p ...

然后进行“改进”:

  // Example 1(b): An improvement
  //               (when it's possible)
  //
  vector<char> v;

  // ...

  vector<char>::iterator i = v.begin();

  // ... do something with *i ...

但实际上并没有提供令人信服的论据:

  

一般来说,更喜欢使用迭代器并不是一个糟糕的指导原则   当你想指向一个内部的对象时指针   容器。毕竟,迭代器在大多数情况下都是无效的   时间和指针的方式相同,也是迭代器的一个原因   存在是提供一种“指向”包含对象的方法。所以,如果你   有一个选择,更喜欢将迭代器用于容器。

     

不幸的是,你不能总是得到与迭代器相同的效果   你可以用指针指向一个容器。主要有两个   迭代器方法的潜在缺点,当我们应用时   必须继续使用指针:

     
      
  1. 您无法始终方便地使用可以使用指针的迭代器。 (见下面的例子。)

  2.   
  3. 在迭代器是一个对象而不仅仅是一个光头的情况下,使用迭代器可能会产生额外的空间和性能开销   指针。

  4.   

在向量的情况下,迭代器只是一个RandomAccessIterator。对于所有意图和目的,这是指针上的薄包装。一种实现甚至承认这一点:

   // This iterator adapter is 'normal' in the sense that it does not
   // change the semantics of any of the operators of its iterator
   // parameter.  Its primary purpose is to convert an iterator that is
   // not a class, e.g. a pointer, into an iterator that is a class.
   // The _Container parameter exists solely so that different containers
   // using this template can instantiate different types, even if the
   // _Iterator parameter is the same.

此外,实现存储_Iterator类型的成员值,即pointerT*。换句话说,只是一个指针。此外,此类型的difference_typestd::ptrdiff_t,定义的操作只是薄包装(即operator++++_pointeroperator*为{{1}等等。

遵循Sutter的论证,这个迭代器类没有提供超越指针的好处,只有缺点。我是对的吗?

3 个答案:

答案 0 :(得分:25)

对于向量,在非通用代码中,您大多是正确的。

好处是,无论迭代器迭代哪个容器,无论该容器是否具有连续存储(以及指针迭代器),您都可以将 RandomAccessIterator 传递给一大堆算法。这是一种抽象。

(除其他外,这种抽象允许实现将基本指针实现换成更性感的东西,比如范围检查迭代器以供调试使用。)

除非你真的不能,否则通常认为使用迭代器是个好习惯。毕竟,习惯会产生一致性,而且一致性会导致可维护性。

迭代器也是以指针不是的方式自我记录。 int*指向什么?不知道。 std::vector<int>::iterator指向什么? AHA ...

最后,它们提供了一种类型安全性的度量 - 尽管这些迭代器可能只是指针周围的瘦包装器,它们不需要指针:如果迭代器是一个独特的类型而不是类型别名,那么你不会意外地将你的迭代器传递到你不想去的地方,或者意外地将它设置为“NULL”。

我同意Sutter的论点与他的大多数其他论点一样令人信服,即不是很好。

答案 1 :(得分:10)

更喜欢迭代器而不是指针的一个真实原因是它们可以在调试版本中实现为checked iterators,并帮助您尽早发现一些令人讨厌的问题。即:

vector<int>::iterator it; // uninitialized iterator
it++;

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers

答案 2 :(得分:10)

  

您无法始终方便地使用可以使用指针的迭代器

这是的缺点之一。有时将指针传递到你真正不希望它们去的地方太“方便”了。具有单独的类型有助于验证参数。

一些早期的实现使用了T*用于vector :: iterator,但是它引起了各种各样的问题,例如人们不小心将不相关的指针传递给向量成员函数。或者为迭代器分配NULL。

  

如果迭代器是一个对象而不仅仅是一个光头指针,那么使用迭代器可能会产生额外的空间和性能开销。

这是在1999年写的,当时我们也认为应该针对不同的容器类型优化<algorithm>中的代码。不久之后,每个人都惊讶地看到编译器自己想出来了。使用迭代器的通用算法运行得很好!

对于std :: vector,使用迭代器而不是指针绝对没有时间开销。您发现迭代器类只是指针上的一个瘦包装器。编译器也会看到它,并生成等效的代码。