【问题标题】:For a vector, why prefer an iterator over a pointer?对于向量,为什么更喜欢迭代器而不是指针?
【发布时间】:2015-12-18 12:59:27
【问题描述】:

在 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 ...

但并没有真正提供令人信服的论据:

一般来说,更喜欢使用迭代器并不是一个糟糕的指导方针 当您想要指向位于 a 中的对象时,指针 容器。毕竟,迭代器在几乎相同的情况下失效 时间和与指针相同的方式,以及迭代器的原因之一 存在是提供一种“指向”包含对象的方法。所以,如果你 有一个选择,更喜欢在容器中使用迭代器。

不幸的是,迭代器无法始终获得相同的效果 您可以使用指向容器的指针。主要有两个 迭代器方法的潜在缺点,当任何一个适用时,我们 不得不继续使用指针:

  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 类型的成员值,即pointerT*。换句话说,只是一个指针。此外,这种类型的difference_typestd::ptrdiff_t,定义的操作只是简单的包装(即operator++++_pointeroperator**_pointer)等等。

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

【问题讨论】:

  • 没有restrict,谈论它们的性能是没有意义的。
  • 似乎问题在于通过调用指针迭代器的组织薄包装器来欺骗自己。如果您将指针称为指针并将迭代器术语保留为仅指向容器的一系列成员中的一个的事物等,那么它将成为具有保护措施的迭代器和没有保护措施的指针之间的真正选择。

标签: c++ pointers vector iterator gotw


【解决方案1】:

对于向量,在非泛型代码中,您基本上是正确的。

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

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

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

迭代器也以指针没有的方式自我记录。 int* 指向什么?不知道。 std::vector&lt;int&gt;::iterator 指向什么?啊哈……

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

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

【讨论】:

  • 'std::vector::iterator 指向什么?啊哈……' +1
【解决方案2】:

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

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

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

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

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

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

【讨论】:

  • “有一个单独的类型有助于验证参数” 现在它们不仅仅是类型别名吗?很好。
  • 啊,是的。我有一种预感,它与类型安全有关。感谢您指出我在概念化方面遇到的困难。
【解决方案3】:

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

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

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

【讨论】:

  • 向量迭代器有一个默认构造函数有点奇怪。
  • @Viktor 从技术上讲,它不是未初始化的。 RandomAccessIterator 是 ForwardIterator,它必须是 DefaultConstructible(因此,值是可初始化的)。我引用的实现就是这样做的:__normal_iterator() : _M_current(_Iterator()) { } 当然,这并没有使it++; 的定义更加明确,但仍然如此。
  • @user5360395 啊,想想我猜有些算法需要默认构造的迭代器什么的。
  • @ViktorSehr 不是。您可能想要一个未初始化的迭代器,原因与您可能想要一个未初始化的其他任何东西相同。 (例如,您可能有条件地为它分配一些东西,然后有条件地使用它)
  • 我猜 Nemanja 的意思是“单数”或“未初始化为默认值以外的特定值”意义上的“未初始化”。我不会那样用这个词,标准也不会,但我看到有些人这样做。
猜你喜欢
  • 2012-12-25
  • 2015-08-13
  • 1970-01-01
  • 1970-01-01
  • 2011-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多