22

In Herb Sutter's When Is a Container Not a Container?, he shows an example of taking a pointer into a container:

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

  // ...

  char* p = &v[0];

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

Then follows it up with an "improvement":

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

  // ...

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

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

But doesn't really provide a convincing argument:

In general, it's not a bad guideline to prefer using iterators instead of pointers when you want to point at an object that's inside a container. After all, iterators are invalidated at mostly the same times and the same ways as pointers, and one reason that iterators exist is to provide a way to "point" at a contained object. So, if you have a choice, prefer to use iterators into containers.

Unfortunately, you can't always get the same effect with iterators that you can with pointers into a container. There are two main potential drawbacks to the iterator method, and when either applies we have to continue to use pointers:

  1. You can't always conveniently use an iterator where you can use a pointer. (See example below.)

  2. Using iterators might incur extra space and performance overhead, in cases where the iterator is an object and not just a bald pointer.

In the case of a vector, the iterator is just a RandomAccessIterator. For all intents and purposes this is a thin wrapper over a pointer. One implementation even acknowledges this:

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

Furthermore, the implementation stores a member value of type _Iterator, which is pointer or T*. In other words, just a pointer. Furthermore, the difference_type for such a type is std::ptrdiff_t and the operations defined are just thin wrappers (i.e., operator++ is ++_pointer, operator* is *_pointer) and so on.

Following Sutter's argument, this iterator class provides no benefits over pointers, only drawbacks. Am I correct?

Barry
  • 286,269
  • 29
  • 621
  • 977
user5360395
  • 229
  • 2
  • 4
  • 1
    Without `restrict`, it makes little sense to talk performance on either of them. – user3528438 Sep 21 '15 at 20:21
  • 2
    It seems the problem is in fooling yourself by calling tissue thin wrappers of pointers iterators. If you call a pointer a pointer and leave the iterator term for something that only ever points to one of a succession of members of a container etc then it becomes a true choice between an iterator with safeguards and a pointer without. – Paddy3118 Sep 21 '15 at 21:39

3 Answers3

25

For vectors, in non-generic code, you're mostly correct.

The benefit is that you can pass a RandomAccessIterator to a whole bunch of algorithms no matter what container the iterator iterates, whether that container has contiguous storage (and thus pointer iterators) or not. It's an abstraction.

(This abstraction, among other things, allows implementations to swap out the basic pointer implementation for something a little more sexy, like range-checked iterators for debug use.)

It's generally considered to be a good habit to use iterators unless you really can't. After all, habit breeds consistency, and consistency leads to maintainability.

Iterators are also self-documenting in a way that pointers are not. What does a int* point to? No idea. What does an std::vector<int>::iterator point to? Aha…

Finally, they provide a measure a type safety — though such iterators may only be thin wrappers around pointers, they needn't be pointers: if an iterator is a distinct type rather than a type alias, then you won't be accidentally passing your iterator into places you didn't want it to go, or setting it to "NULL" accidentally.

I agree that Sutter's argument is about as convincing as most of his other arguments, i.e. not very.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
11

You can't always conveniently use an iterator where you can use a pointer

That is not one of the disadvantages. Sometimes it is just too "convenient" to get the pointer passed to places where you really didn't want them to go. Having a separate type helps in validating parameters.

Some early implementations used T* for vector::iterator, but it caused various problems, like people accidentally passing an unrelated pointer to vector member functions. Or assigning NULL to the iterator.

Using iterators might incur extra space and performance overhead, in cases where the iterator is an object and not just a bald pointer.

This was written in 1999, when we also believed that code in <algorithm> should be optimized for different container types. Not much later everyone was surprised to see that the compilers figured that out themselves. The generic algorithms using iterators worked just fine!

For a std::vector there is absolutely no space of time overhead for using an iterator instead of a pointer. You found out that the iterator class is just a thin wrapper over a pointer. Compilers will also see that, and generate equivalent code.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
10

One real-life reason to prefer iterators over pointers is that they can be implemented as checked iterators in debug builds and help you catch some nasty problems early. I.e:

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

or

for (it = vec1.begin(); it != vec2.end(); ++it) // different containers
Nemanja Trifunovic
  • 24,346
  • 3
  • 50
  • 88
  • 1
    It's a little bit wierd that a vector iterator has a default constructor. – Viktor Sehr Sep 21 '15 at 19:02
  • @Viktor Technically, it's not uninitialized. A RandomAccessIterator is a ForwardIterator, which is required to be DefaultConstructible (and as a result, value initializable). The implementation I reference does just this: `__normal_iterator() : _M_current(_Iterator()) { }` Of course, this doesn't make `it++;` any more well-defined, but still. – user5360395 Sep 21 '15 at 19:22
  • @user5360395 Ah, thinking about it I guess some algorithms requires default constructed iterators or something. – Viktor Sehr Sep 21 '15 at 21:02
  • @ViktorSehr Not really. You might want an uninitialized iterator for the same reason you might want an uninitialized anything else. (For example, you might be conditionally assigning something to it, then conditionally using it later) – user253751 Sep 21 '15 at 22:57
  • 1
    I guess Nemanja meant "uninitialised" in the sense of "singular" or "not initialised to a specific value other than the default". I wouldn't use the term that way, and neither does the standard, but I've seen that some people do. – Lightness Races in Orbit Sep 22 '15 at 11:03