22

在 Herb Sutter'sWhen 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. 在迭代器是一个对象而不仅仅是一个光秃秃的指针的情况下,使用迭代器可能会产生额外的空间和性能开销。

在向量的情况下,迭代器只是一个 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此外,该实现存储类型为pointeror的成员值T*。换句话说,只是一个指针。此外,difference_type这种类型的 isstd::ptrdiff_t和定义的操作只是简单的包装器(即operator++is ++_pointeroperator*is *_pointer)等等。

按照 Sutter 的论点,这个迭代器类对指针没有任何好处,只有缺点。我对么?

4

3 回答 3

25

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

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

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

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

迭代器也以指针没有的方式自我记录。一个点是什么意思int*?不知道。一个点是什么意思std::vector<int>::iterator?啊哈……

最后,它们提供了一种类型安全的度量——尽管这样的迭代器可能只是指针的薄包装,但它们不一定指针:如果迭代器是不同的类型而不是类型别名,那么你就不会意外地传递你的迭代器进入您不希望它去的地方,或者意外地将其设置为“NULL”。

我同意萨特的论点与他的大多数其他论点一样令人信服,即不是非常有说服力。

于 2015-09-21T18:04:58.133 回答
11

您不能总是方便地使用可以使用指针的迭代器

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

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

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

这是在 1999 年编写的,当时我们还认为<algorithm>应该针对不同的容器类型优化代码。不久之后,每个人都惊讶地发现编译器自己解决了这个问题。使用迭代器的通用算法工作得很好!

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

于 2015-09-21T18:53:27.350 回答
10

选择迭代器而不是指针的一个现实原因是,它们可以在调试版本中实现为检查迭代器,并帮助您及早发现一些令人讨厌的问题。IE:

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

或者

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers
于 2015-09-21T18:21:17.297 回答