Directory listing of http: uap.unnes.ac.id ebook electronic book 1 C++ Programming Language 3rd E

________________________________________

________________________________________________________________________________________________________________________________________________________________

19
________________________________________

________________________________________________________________________________________________________________________________________________________________

Iterators and Allocators
The reason that data structures and algorithms
can work together seamlessly is ... that they
do not know anything about each other.
– Alex Stepanov

Iterators and sequences — operations on iterators — iterator traits — iterator categories
— inserters — reverse iterators — stream iterators — checked iterators — exceptions
and algorithms — allocators — the standard aallllooccaattoorr — user-defined allocators —
low-level memory functions — advice — exercises.

19.1 Introduction [iter.intro]

Iterators are the glue that holds containers and algorithms together. They provide an abstract view
of data so that the writer of an algorithm need not be concerned with concrete details of a myriad of
data structures. Conversely, the standard model of data access provided by iterators relieves containers from having to provide a more extensive set of access operations. Similarly, allocators are
used to insulate container implementations from details of access to memory.
Iterators support an abstract model of data as sequences of objects (§19.2). Allocators provide a
mapping from a lower-level model of data as arrays of bytes into the higher-level object model
(§19.4). The most common lower-level memory model is itself supported by a few standard functions (§19.4.4).
Iterators are a concept with which every programmer should be familiar. In contrast, allocators
are a support mechanism that a programmer rarely needs to worry about and few programmers will
ever need to write a new allocator.

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

550

Iterators and Allocators

Chapter 19


19.2 Iterators and Sequences [iter.iter]
An iterator is a pure abstraction. That is, anything that behaves like an iterator is an iterator
(§3.8.2). An iterator is an abstraction of the notion of a pointer to an element of a sequence. Its key
concepts are
– ‘‘the element currently pointed to’’ (dereferencing, represented by operators * and ->),
– ‘‘point to next element’’ (increment, represented by operator ++), and
– equality (represented by operator ==).
For example, the built-in type iinntt* is an iterator for an iinntt[] and the class lliisstt::iitteerraattoorr is an
iterator for a lliisstt class.
A sequence is an abstraction of the notion ‘‘something where we can get from the beginning to
the end by using a next-element operation:’’
begin()
elem[0]

end()
elem[1]

elem[2]

...


elem[n-1]

.............
.
.
.
.
.
.
.............

Examples of such sequences are arrays (§5.2), vectors (§16.3), singly-linked lists (§17.8[17]),
doubly-linked lists (§17.2.2), trees (§17.4.1), input (§21.3.1), and output (§21.2.1). Each has its
own appropriate kind of iterator.
The iterator classes and functions are declared in namespace ssttdd and found in .
An iterator is not a general pointer. Rather, it is an abstraction of the notion of a pointer into an
array. There is no concept of a ‘‘null iterator.’’ The test to determine whether an iterator points to
an element or not is conventionally done by comparing it against the end of its sequence (rather
than comparing it against a nnuullll element). This notion simplifies many algorithms by removing the

need for a special end case and generalizes nicely to sequences of arbitrary types.
An iterator that points to an element is said to be valid and can be dereferenced (using *, [], or
-> appropriately). An iterator can be invalid either because it hasn’t been initialized, because it
pointed into a container that was explicitly or implicitly resized (§16.3.6, §16.3.8), because the container into which it pointed was destroyed, or because it denotes the end of a sequence (§18.2). The
end of a sequence can be thought of as an iterator pointing to a hypothetical element position onepast-the-last element of a sequence.
19.2.1 Iterator Operations [iter.oper]

Not every kind of iterator supports exactly the same set of operations. For example, reading
requires different operations from writing, and a vveeccttoorr allows convenient and efficient random
access in a way that would be prohibitively expensive to provide for a lliisstt or an iissttrreeaam
m. Consequently, we classify iterators into five categories according to the operations they are capable of
providing efficiently (that is, in constant time; §17.1):

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.1

Iterator Operations


551

__________________________________________________________________________
___________________________________________________________________________

Iterator Operations and Categories
_________________________________________________________________________


output
input
forward bidirectional random-access
 Category:

Abbreviation:  O
Ouutt
IInn
F
Foorr
B

Bii
R
Raann
__________________________________________________________________________


 Read:

=*p
=*p
=*p
=*p

 Access:

->
->
->
-> []




 *p=
*p=
*p=
*p=
 Write:


++
++
++
++ -++ -- + - += -= 
 Iteration:
__________________________________________________________________________
Comparison: 
== !=
== !=
== !=
== != < > >= ()

ttyyppeeddeeff ttyyppeennaam
mee IItteerr::rreeffeerreennccee rreeffeerreennccee;
// return type of operator*()
};

The ddiiffffeerreennccee__ttyyppee is the type used to represent the difference between two iterators, and the
iitteerraattoorr__ccaatteeggoorryy is a type indicating what operations the iterator supports. For ordinary pointers,
specializations (§13.5) for and are provided. In particular:
tteem
mppllaattee ssttrruucctt iitteerraattoorr__ttrraaiittss {
// specialization for pointers
ttyyppeeddeeff rraannddoom
m__aacccceessss__iitteerraattoorr__ttaagg iitteerraattoorr__ccaatteeggoorryy;
ttyyppeeddeeff T vvaalluuee__ttyyppee;
ttyyppeeddeeff ppttrrddiiffff__tt ddiiffffeerreennccee__ttyyppee;
ttyyppeeddeeff T
T* ppooiinntteerr;
ttyyppeeddeeff T
T& rreeffeerreennccee;
};


That is, the difference between two pointers is represented by the standard library type ppttrrddiiffff__tt
from (§6.2.1) and a pointer provides random access (§19.2.3). Given iitteerraattoorr__ttrraaiittss,
we can write code that depends on properties of an iterator parameter. The ccoouunntt() algorithm is
the classical example:
tteem
mppllaattee
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee ccoouunntt(IInn ffiirrsstt, IInn llaasstt, ccoonnsstt T
T& vvaall)
{
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee rreess = 00;
w
whhiillee (ffiirrsstt != llaasstt) iiff (*ffiirrsstt++ == vvaall) ++rreess;
rreettuurrnn rreess;
}

Here, the type of the result is expressed in terms of the iitteerraattoorr__ttrraaiittss of the input. This technique
is necessary because there is no language primitive for expressing an arbitrary type in terms of

another.
Instead of using iitteerraattoorr__ttrraaiittss, we might have specialized ccoouunntt() for pointers:
tteem
mppllaattee
ttyyppeennaam
mee IInn::ddiiffffeerreennccee__ttyyppee ccoouunntt(IInn ffiirrsstt, IInn llaasstt, ccoonnsstt T
T& vvaall);
tteem
mppllaattee ppttrrddiiffff__tt ccoouunntt(T
T* ffiirrsstt, T
T* llaasstt, ccoonnsstt T
T& vvaall);

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.2

Iterator Traits


553

However, this would have solved the problem for ccoouunntt() only. Had we used this technique for a
dozen algorithms, the information about distance types would have been replicated a dozen times.
In general, it is better to represent a design decision in one place (§23.4.2). In that way, the decision can – if necessary – be changed in one place.
Because iitteerraattoorr__ttrraaiittss is defined for every iterator, we implicitly define an
iitteerraattoorr__ttrraaiittss whenever we design a new iterator type. If the default traits generated from the
general iitteerraattoorr__ttrraaiittss template are not right for our new iterator type, we provide a specialization
in a way similar to what the standard library does for pointer types. The iitteerraattoorr__ttrraaiittss that are
implicitly generated assume that the iterator is a class with the member types ddiiffffeerreennccee__ttyyppee,
vvaalluuee__ttyyppee, etc. In , the library provides a base type that can be used to define those
member types:
tteem
mppllaattee
ssttrruucctt iitteerraattoorr {
ttyyppeeddeeff C
Caatt iitteerraattoorr__ccaatteeggoorryy; // §19.2.3
ttyyppeeddeeff T vvaalluuee__ttyyppee;
// type of element
ttyyppeeddeeff D
Diisstt ddiiffffeerreennccee__ttyyppee; // type of iterator difference
ttyyppeeddeeff P
Pttrr ppooiinntteerr;
// return type for – >
ttyyppeeddeeff R
Reeff rreeffeerreennccee;
// return type for *
};

Note that rreeffeerreennccee and ppooiinntteerr are not iterators. They are intended to be the return types of ooppeerr-aattoorr*() and ooppeerraattoorr->(), respectively, for some iterator.
The iitteerraattoorr__ttrraaiittss are the key to the simplicity of many interfaces that rely on iterators and to
the efficient implementation of many algorithms.
19.2.3 Iterator Categories [iter.cat]
The different kinds of iterators – usually referred to as iterator categories – fit into a hierarchical
ordering:

Input
Forward

Bidirectional

Random access

Output

This is not a class inheritance diagram. An iterator category is a classification of a type based on
the operations it provides. Many otherwise unrelated types can belong to the same iterator category. For example, both ordinary pointers (§19.2.2) and C
Chheecckkeedd__iitteerrs (§19.3) are random-access
iterators.
As noted in Chapter 18, different algorithms require different kinds of iterators as arguments.
Also, the same algorithm can sometimes be implemented with different efficiencies for different
kinds of iterators. To support overload resolution based on iterator categories, the standard library
provides five classes representing the five iterator categories:

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

554

Iterators and Allocators

ssttrruucctt
ssttrruucctt
ssttrruucctt
ssttrruucctt
ssttrruucctt

Chapter 19

iinnppuutt__iitteerraattoorr__ttaagg {};
oouuttppuutt__iitteerraattoorr__ttaagg {};
ffoorrw
waarrdd__iitteerraattoorr__ttaagg : ppuubblliicc iinnppuutt__iitteerraattoorr__ttaagg {};
bbiiddiirreeccttiioonnaall__iitteerraattoorr__ttaagg: ppuubblliicc ffoorrw
waarrdd__iitteerraattoorr__ttaagg {};
rraannddoom
m__aacccceessss__iitteerraattoorr__ttaagg: ppuubblliicc bbiiddiirreeccttiioonnaall__iitteerraattoorr__ttaagg {};

Looking at the operations supported by input and forward iterators (§19.2.1), we would expect
ffoorrw
waarrdd__iitteerraattoorr__ttaagg to be derived from oouuttppuutt__iitteerraattoorr__ttaagg as well as from iinnppuutt__iitteerraattoorr__ttaagg.
The reasons that it is not are obscure and probably invalid. However, I have yet to see an example
in which that derivation would have simplified real code.
The inheritance of tags is useful (only) to save us from defining separate versions of a function
where several – but not all – kinds of iterators can use the same algorithms. Consider how to
implement ddiissttaannccee:
tteem
mppllaattee
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee ddiissttaannccee(IInn ffiirrsstt, IInn llaasstt);

There are two obvious alternatives:
[1] If IInn is a random-access iterator, we can subtract ffiirrsstt from llaasstt.
[2] Otherwise, we must increment an iterator from ffiirrsstt to llaasstt and count the distance.
We can express these two alternatives as a pair of helper functions:
tteem
mppllaattee
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee
ddiisstt__hheellppeerr(IInn ffiirrsstt, IInn llaasstt, iinnppuutt__iitteerraattoorr__ttaagg)
{
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee d = 00;
w
whhiillee (ffiirrsstt++!=llaasstt) dd++;
// use increment only
rreettuurrnn dd;
}
tteem
mppllaattee
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee
ddiisstt__hheellppeerr(R
Raann ffiirrsstt, R
Raann llaasstt, rraannddoom
m__aacccceessss__iitteerraattoorr__ttaagg)
{
rreettuurrnn llaasstt-ffiirrsstt;
// rely on random access
}

The iterator category tag arguments make it explicit what kind of iterator is expected. The iterator
tag is used exclusively for overload resolution; the tag takes no part in the actual computation. It is
a purely compile-time selection mechanism. In addition to automatic selection of a helper function,
this technique provides immediate type checking (§13.2.5).
It is now trivial to define ddiissttaannccee() by calling the appropriate helper function:
tteem
mppllaattee
ttyyppeennaam
mee iitteerraattoorr__ttrraaiittss::ddiiffffeerreennccee__ttyyppee ddiissttaannccee(IInn ffiirrsstt, IInn llaasstt)
{
rreettuurrnn ddiisstt__hheellppeerr(ffiirrsstt,llaasstt,iitteerraattoorr__ttrraaiittss::iitteerraattoorr__ccaatteeggoorryy());
}

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.3

Iterator Categories

555

For a ddiisstt__hheellppeerr() to be called, the iitteerraattoorr__ttrraaiittss::iitteerraattoorr__ccaatteeggoorryy used must be a
iinnppuutt__iitteerraattoorr__ttaagg or a rraannddoom
m__aacccceessss__iitteerraattoorr__ttaagg. However, there is no need for separate versions of ddiisstt__hheellppeerr() for forward or bidirectional iterators. Thanks to tag inheritance, those cases
are handled by the ddiisstt__hheellppeerr() which takes an iinnppuutt__iitteerraattoorr__ttaagg. The absence of a version for
oouuttppuutt__iitteerraattoorr__ttaagg reflects the fact that ddiissttaannccee() is not meaningful for output iterators:
vvooiidd ff(vveeccttoorr& vvii,
lliisstt& lldd,
iissttrreeaam
m__iitteerraattoorr& iiss11, iissttrreeaam
m__iitteerraattoorr& iiss22,
oossttrreeaam
m__iitteerraattoorr& ooss11, oossttrreeaam
m__iitteerraattoorr& ooss22)
{
ddiissttaannccee(vvii.bbeeggiinn(),vvii.eenndd());
// use subtraction algorithm
ddiissttaannccee(lldd.bbeeggiinn(),lldd.eenndd());
// use increment algorithm
ddiissttaannccee(iiss11,iiss22);
// use increment algorithm
ddiissttaannccee(ooss11,ooss22); // error: wrong iterator category, dist_helper() argument type mismatch
}

Calling ddiissttaannccee() for an iissttrreeaam
m__iitteerraattoorr probably doesn’t make much sense in a real program,
though. The effect would be to read the input, throw it away, and return the number of values
thrown away.
Using iitteerraattoorr__ttrraaiittss::iitteerraattoorr__ccaatteeggoorryy allows a programmer to provide alternative
implementations so that a user who cares nothing about the implementation of algorithms automatically gets the most appropriate implementation for each data structure used. In other words, it
allows us to hide an implementation detail behind a convenient interface. Inlining can be used to
ensure that this elegance is not bought at the cost of run-time efficiency.
19.2.4 Inserters [iter.insert]
Producing output through an iterator into a container implies that elements following the one
pointed to by the iterator can be overwritten. This implies the possibility of overflow and consequent memory corruption. For example:
vvooiidd ff(vveeccttoorr& vvii)
{
ffiillll__nn(vvii.bbeeggiinn(),220000,77);
}

// assign 7 to vi[0]..[199]

If vvii has fewer than 220000 elements, we are in trouble.
In , the standard library provides three iterator template classes to deal with this
problem, plus three functions to make it convenient to use those iterators:
tteem
mppllaattee bbaacckk__iinnsseerrtt__iitteerraattoorr bbaacckk__iinnsseerrtteerr(C
Coonntt& cc);
tteem
mppllaattee ffrroonntt__iinnsseerrtt__iitteerraattoorr ffrroonntt__iinnsseerrtteerr(C
Coonntt& cc);
tteem
mppllaattee iinnsseerrtt__iitteerraattoorr iinnsseerrtteerr(C
Coonntt& cc, O
Ouutt pp);

The bbaacckk__iinnsseerrtteerr() causes elements to be added to the end of the container, ffrroonntt__iinnsseerrtteerr()
causes elements to be added to the front, and ‘‘plain’’ iinnsseerrtteerr() causes elements to be added
before its iterator argument. For iinnsseerrtteerr(cc,pp), p must be a valid iterator for cc. Naturally, a container grows each time a value is written to it through an insert iterator.

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

556

Iterators and Allocators

Chapter 19

When written to, an inserter inserts a new element into a sequence using ppuusshh__bbaacckk(),
ppuusshh__ffrroonntt(), or iinnsseerrtt() (§16.3.6) rather than overwriting an existing element. For example:
vvooiidd gg(vveeccttoorr& vvii)
{
ffiillll__nn(bbaacckk__iinnsseerrtteerr(vvii),220000,77);
}

// add 200 7s to the end of vi

Inserters are as simple and efficient as they are useful. For example:
tteem
mppllaattee
ccllaassss iinnsseerrtt__iitteerraattoorr : ppuubblliicc iitteerraattoorr {
pprrootteecctteedd:
C
Coonntt& ccoonnttaaiinneerr;
// container to insert into
ttyyppeennaam
mee C
Coonntt::iitteerraattoorr iitteerr; // points into the container
ppuubblliicc:
eexxpplliicciitt iinnsseerrtt__iitteerraattoorr(C
Coonntt& xx, ttyyppeennaam
mee C
Coonntt::iitteerraattoorr ii)
: ccoonnttaaiinneerr(xx), iitteerr(ii) {}
iinnsseerrtt__iitteerraattoorr& ooppeerraattoorr=(ccoonnsstt ttyyppeennaam
mee C
Coonntt::vvaalluuee__ttyyppee& vvaall)
{
iitteerr = ccoonnttaaiinneerr.iinnsseerrtt(iitteerr,vvaall);
++iitteerr;
rreettuurrnn *tthhiiss;
}
iinnsseerrtt__iitteerraattoorr& ooppeerraattoorr*() { rreettuurrnn *tthhiiss; }
iinnsseerrtt__iitteerraattoorr& ooppeerraattoorr++() { rreettuurrnn *tthhiiss; }
iinnsseerrtt__iitteerraattoorr ooppeerraattoorr++(iinntt) { rreettuurrnn *tthhiiss; }

// prefix ++
// postfix ++

};

Clearly, inserters are output iterators.
An iinnsseerrtt__iitteerraattoorr is a special case of an output sequence. In parallel to the iisseeqq from §18.3.1,
we might define:
tteem
mppllaattee
iinnsseerrtt__iitteerraattoorr
oosseeqq(C
Coonntt& cc, ttyyppeennaam
mee C
Coonntt::iitteerraattoorr ffiirrsstt, ttyyppeennaam
mee C
Coonntt::iitteerraattoorr llaasstt)
{
rreettuurrnn iinnsseerrtt__iitteerraattoorr(cc,cc.eerraassee(ffiirrsstt,llaasstt)); // erase is explained in §16.3.6
}

In other words, an output sequence removes its old elements and replaces them with the output.
For example:
vvooiidd ff(lliisstt& llii,vveeccttoorr& vvii) // replace second half of vi by a copy of li
{
ccooppyy(llii.bbeeggiinn(),llii.eenndd(),oosseeqq(vvii,vvii+vvii.ssiizzee()/22,vvii.eenndd()));
}

The container needs to be an argument to an oosseeqq because it is not possible to decrease the size of a
container, given only iterators into it (§18.6, §18.6.3).

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.5

Reverse Iterators

557

19.2.5 Reverse Iterators [iter.reverse]
The standard containers provide rrbbeeggiinn() and rreenndd() for iterating through elements in reverse
order (§16.3.2). These member functions return rreevveerrssee__iitteerraattoorrs:
tteem
mppllaattee
ccllaassss rreevveerrssee__iitteerraattoorr : ppuubblliicc iitteerraattoorr {
pprrootteecctteedd:
IItteerr ccuurrrreenntt; // current points to the element after the one *this refers to.
ppuubblliicc:
ttyyppeeddeeff IItteerr iitteerraattoorr__ttyyppee;
rreevveerrssee__iitteerraattoorr() : ccuurrrreenntt() { }
eexxpplliicciitt rreevveerrssee__iitteerraattoorr(IItteerr xx) : ccuurrrreenntt(xx) { }
tteem
mppllaattee rreevveerrssee__iitteerraattoorr(ccoonnsstt rreevveerrssee__iitteerraattoorr& xx) : ccuurrrreenntt(xx.bbaassee()) { }
IItteerr bbaassee() ccoonnsstt { rreettuurrnn ccuurrrreenntt; } // current iterator value
rreeffeerreennccee ooppeerraattoorr*() ccoonnsstt { IItteerr ttm
mpp = ccuurrrreenntt; rreettuurrnn *--ttm
mpp; }
ppooiinntteerr ooppeerraattoorr->() ccoonnsstt;
rreeffeerreennccee ooppeerraattoorr[](ddiiffffeerreennccee__ttyyppee nn) ccoonnsstt;
rreevveerrssee__iitteerraattoorr& ooppeerraattoorr++() { --ccuurrrreenntt; rreettuurrnn *tthhiiss; }
// note: not ++
rreevveerrssee__iitteerraattoorr ooppeerraattoorr++(iinntt) { rreevveerrssee__iitteerraattoorr t = ccuurrrreenntt; --ccuurrrreenntt; rreettuurrnn tt; }
rreevveerrssee__iitteerraattoorr& ooppeerraattoorr--() { ++ccuurrrreenntt; rreettuurrnn *tthhiiss; }
// note: not – –
rreevveerrssee__iitteerraattoorr ooppeerraattoorr--(iinntt) { rreevveerrssee__iitteerraattoorr t = ccuurrrreenntt; ++ccuurrrreenntt; rreettuurrnn tt; }
rreevveerrssee__iitteerraattoorr ooppeerraattoorr+(ddiiffffeerreennccee__ttyyppee nn) ccoonnsstt;
rreevveerrssee__iitteerraattoorr& ooppeerraattoorr+=(ddiiffffeerreennccee__ttyyppee nn);
rreevveerrssee__iitteerraattoorr ooppeerraattoorr-(ddiiffffeerreennccee__ttyyppee nn) ccoonnsstt;
rreevveerrssee__iitteerraattoorr& ooppeerraattoorr-=(ddiiffffeerreennccee__ttyyppee nn);
};

A rreevveerrssee__iitteerraattoorr is implemented using an iitteerraattoorr called ccuurrrreenntt. That iitteerraattoorr can (only) point
to the elements of its sequence plus its one-past-the-end element. However, the rreevveerrssee__iitteerraattoorr’s
one-past-the-end element is the original sequence’s (inaccessible) one-before-the-beginning element. Thus, to avoid access violations, ccuurrrreenntt points to the element after the one the
rreevveerrssee__iitteerraattoorr refers to. This implies that * returns the value *(ccuurrrreenntt-11) and that ++ is
implemented using -- on ccuurrrreenntt.
A rreevveerrssee__iitteerraattoorr supports the operations that its initializer supports (only). For example:
vvooiidd ff(vveeccttoorr& vv, lliisstt& llsstt)
{
rreevveerrssee__iitteerraattoorr(vv.eenndd())[33] = 77;
// ok: random-access iterator
rreevveerrssee__iitteerraattoorr(llsstt.eenndd())[33] = ´44´;
// error: bidirectional iterator doesn’t support []
*(++++++rreevveerrssee__iitteerraattoorr(llsstt.eenndd())) = ´44´; // ok!
}

In addition, the library provides ==, !=, =, + and - for rreevveerrssee__iitteerraattoorrs.

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

558

Iterators and Allocators

Chapter 19

19.2.6 Stream Iterators [iter.stream]
Ordinarily, I/O is done using the streams library (Chapter 21), a graphical user-interface system
(not covered by the C++ standard), or the C I/O functions (§21.8). These I/O interfaces are primarily aimed at reading and writing individual values of a variety of types. The standard library provides four iterator types to fit stream I/O into the general framework of containers and algorithms:
– oossttrreeaam
m__iitteerraattoorr: for writing to an oossttrreeaam
m (§3.4, §21.2.1).
– iissttrreeaam
m__iitteerraattoorr: for reading from an iissttrreeaam
m (§3.6, §21.3.1).
– oossttrreeaam
mbbuuff__iitteerraattoorr: for writing to a stream buffer (§21.6.1).
– iissttrreeaam
mbbuuff__iitteerraattoorr: for reading from a stream buffer (§21.6.2).
The idea is simply to present input and output of collections as sequences:
tteem
mppllaattee
ccllaassss oossttrreeaam
m__iitteerraattoorr : ppuubblliicc iitteerraattoorr {
ppuubblliicc:
ttyyppeeddeeff C
Chh cchhaarr__ttyyppee;
ttyyppeeddeeff T
Trr ttrraaiittss__ttyyppee;
ttyyppeeddeeff bbaassiicc__oossttrreeaam
m oossttrreeaam
m__ttyyppee;
oossttrreeaam
m__iitteerraattoorr(oossttrreeaam
m__ttyyppee& ss);
oossttrreeaam
m__iitteerraattoorr(oossttrreeaam
m__ttyyppee& ss, ccoonnsstt C
Chh* ddeelliim
m); // write delim after each output value
oossttrreeaam
m__iitteerraattoorr(ccoonnsstt oossttrreeaam
m__iitteerraattoorr&);
~oossttrreeaam
m__iitteerraattoorr();
oossttrreeaam
m__iitteerraattoorr& ooppeerraattoorr=(ccoonnsstt T
T& vvaall);

// write val to output

oossttrreeaam
m__iitteerraattoorr& ooppeerraattoorr*();
oossttrreeaam
m__iitteerraattoorr& ooppeerraattoorr++();
oossttrreeaam
m__iitteerraattoorr& ooppeerraattoorr++(iinntt);
};

This iterator accepts the usual write and increment operations of an output iterator and converts
them into output operations on an oossttrreeaam
m. For example:
vvooiidd ff()
{
oossttrreeaam
m__iitteerraattoorr ooss(ccoouutt);
// write ints to cout through os
*ooss = 77;
// output 7
++ooss;
// get ready for next output
*ooss = 7799;
// output 79
}

The ++ operation might trigger an actual output operation, or it might have no effect. Different
implementations will use different implementation strategies. Consequently, for code to be portable a ++ must occur between every two assignments to an oossttrreeaam
m__iitteerraattoorr. Naturally, every
standard algorithm is written that way – or it would not work for a vveeccttoorr. This is why
oossttrreeaam
m__iitteerraattoorr is defined this way.
An implementation of oossttrreeaam
m__iitteerraattoorr is trivial and is left as an exercise (§19.6[4]). The standard I/O supports different character types; cchhaarr__ttrraaiittss (§20.2) describes the aspects of a character
type that can be important for I/O and ssttrriinnggs.

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.6

Stream Iterators

559

An input iterator for iissttrreeaam
ms is defined analogously:
tteem
mppllaattee
ccllaassss iissttrreeaam
m__iitteerraattoorr : ppuubblliicc iitteerraattoorr {
ppuubblliicc:
ttyyppeeddeeff C
Chh cchhaarr__ttyyppee;
ttyyppeeddeeff T
Trr ttrraaiittss__ttyyppee;
ttyyppeeddeeff bbaassiicc__iissttrreeaam
m iissttrreeaam
m__ttyyppee;
iissttrreeaam
m__iitteerraattoorr();
// end of input
iissttrreeaam
m__iitteerraattoorr(iissttrreeaam
m__ttyyppee& ss);
iissttrreeaam
m__iitteerraattoorr(ccoonnsstt iissttrreeaam
m__iitteerraattoorr&);
~iissttrreeaam
m__iitteerraattoorr();
ccoonnsstt T
T& ooppeerraattoorr*() ccoonnsstt;
ccoonnsstt T
T* ooppeerraattoorr->() ccoonnsstt;
iissttrreeaam
m__iitteerraattoorr& ooppeerraattoorr++();
iissttrreeaam
m__iitteerraattoorr ooppeerraattoorr++(iinntt);
};

This iterator is specified so that what would be conventional use for a container triggers input from
an iissttrreeaam
m. For example:
vvooiidd ff()
{
iissttrreeaam
m__iitteerraattoorr iiss(cciinn);
// read ints from cin through is
iinntt ii11 = *iiss;
// read an int
++iiss;
// get ready for next input
iinntt ii22 = *iiss;
// read an int
}

The default iissttrreeaam
m__iitteerraattoorr represents the end of input so that we can specify an input sequence:
vvooiidd ff(vveeccttoorr& vv)
{
ccooppyy(iissttrreeaam
m__iitteerraattoorr(cciinn),iissttrreeaam
m__iitteerraattoorr(),bbaacckk__iinnsseerrtteerr(vv));
}

To make this work, the standard library supplies == and != for iissttrreeaam
m__iitteerraattoorrs.
An implementation of iissttrreeaam
m__iitteerraattoorr is less trivial than an oossttrreeaam
m__iitteerraattoorr implementation, but it is still simple. Implementing an iissttrreeaam
m__iitteerraattoorr is also left as an exercise (§19.6[5]).
19.2.6.1 Stream Buffers [iter.streambuf]
As described in §21.6, stream I/O is based on the idea of oossttrreeaam
ms and iissttrreeaam
ms filling and emptying buffers from and to which the low-level physical I/O is done. It is possible to bypass the standard iostreams formatting and operate directly on the stream buffers (§21.6.4). That ability is also
provided to algorithms through the notion of iissttrreeaam
mbbuuff__iitteerraattoorrs and oossttrreeaam
mbbuuff__iitteerraattoorrs:

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

560

Iterators and Allocators

Chapter 19

tteem
mppllaattee
ccllaassss iissttrreeaam
mbbuuff__iitteerraattoorr
: ppuubblliicc iitteerraattoorr {
ppuubblliicc:
ttyyppeeddeeff C
Chh cchhaarr__ttyyppee;
ttyyppeeddeeff T
Trr ttrraaiittss__ttyyppee;
ttyyppeeddeeff ttyyppeennaam
mee T
Trr::iinntt__ttyyppee iinntt__ttyyppee;
ttyyppeeddeeff bbaassiicc__ssttrreeaam
mbbuuff ssttrreeaam
mbbuuff__ttyyppee;
ttyyppeeddeeff bbaassiicc__iissttrreeaam
m iissttrreeaam
m__ttyyppee;
ccllaassss pprrooxxyy;

// helper type

iissttrreeaam
mbbuuff__iitteerraattoorr() tthhrroow
w();
// end of buffer
iissttrreeaam
mbbuuff__iitteerraattoorr(iissttrreeaam
m__ttyyppee& iiss) tthhrroow
w(); // read from is’s streambuf
iissttrreeaam
mbbuuff__iitteerraattoorr(ssttrreeaam
mbbuuff__ttyyppee*) tthhrroow
w();
iissttrreeaam
mbbuuff__iitteerraattoorr(ccoonnsstt pprrooxxyy& pp) tthhrroow
w(); // read from p’s streambuf
C
Chh ooppeerraattoorr*() ccoonnsstt;
iissttrreeaam
mbbuuff__iitteerraattoorr& ooppeerraattoorr++();
pprrooxxyy ooppeerraattoorr++(iinntt);

// prefix
// postfix

bbooooll eeqquuaall(iissttrreeaam
mbbuuff__iitteerraattoorr&);

// both or neither streambuf at eof

};

In addition, == and != are supplied.
Reading from a ssttrreeaam
mbbuuff is a lower-level operation than reading from an iissttrreeaam
m. Consequently, the iissttrreeaam
mbbuuff__iitteerraattoorr interface is messier than the iissttrreeaam
m__iitteerraattoorr interface. However, once the iissttrreeaam
mbbuuff__iitteerraattoorr is properly initialized, *, ++, and = have their usual meanings
when used in the usual way.
The pprrooxxyy type is an implementation-defined helper type that allows the postfix ++ to be implemented without imposing constraints on the ssttrreeaam
mbbuuff implementation. A pprrooxxyy holds the result
value while the iterator is incremented:
tteem
mppllaattee
ccllaassss iissttrreeaam
mbbuuff__iitteerraattoorr::pprrooxxyy {
C
Chh vvaall;
bbaassiicc__iissttrreeaam
mbbuuff* bbuuff;
pprrooxxyy(C
Chh vv, bbaassiicc__iissttrreeaam
mbbuuff* bb) :vvaall(vv), bbuuff(bb) { }
ppuubblliicc:
C
Chh ooppeerraattoorr*() { rreettuurrnn vvaall; }
};

An oossttrreeaam
mbbuuff__iitteerraattoorr is defined similarly:
tteem
mppllaattee
ccllaassss oossttrreeaam
mbbuuff__iitteerraattoorr : ppuubblliicc iitteerraattoorr{
ppuubblliicc:
ttyyppeeddeeff C
Chh cchhaarr__ttyyppee;
ttyyppeeddeeff T
Trr ttrraaiittss__ttyyppee;

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

Section 19.2.6.1

Stream Buffers

561

ttyyppeeddeeff bbaassiicc__ssttrreeaam
mbbuuff ssttrreeaam
mbbuuff__ttyyppee;
ttyyppeeddeeff bbaassiicc__oossttrreeaam
m oossttrreeaam
m__ttyyppee;
oossttrreeaam
mbbuuff__iitteerraattoorr(oossttrreeaam
m__ttyyppee& ooss) tthhrroow
w();
oossttrreeaam
mbbuuff__iitteerraattoorr(ssttrreeaam
mbbuuff__ttyyppee*) tthhrroow
w();
oossttrreeaam
mbbuuff__iitteerraattoorr& ooppeerraattoorr=(C
Chh);

// write to os’s streambuf

oossttrreeaam
mbbuuff__iitteerraattoorr& ooppeerraattoorr*();
oossttrreeaam
mbbuuff__iitteerraattoorr& ooppeerraattoorr++();
oossttrreeaam
mbbuuff__iitteerraattoorr& ooppeerraattoorr++(iinntt);
bbooooll ffaaiilleedd() ccoonnsstt tthhrroow
w();

// true if Tr::eof() seen

};

19.3 Checked Iterators [iter.checked]
A programmer can provide iterators in addition to those provided by the standard library. This is
often necessary when providing a new kind of container, and sometimes a new kind of iterator is a
good way to support a different way of using existing containers. As an example, I here describe
an iterator that range checks access to its container.
Using standard containers reduces the amount of explicit memory management. Using standard
algorithms reduces the amount of explicit addressing of elements in containers. Using the standard
library together with language facilities that maintain type safety dramatically reduces run-time
errors compared to traditional C coding styles. However, the standard library still relies on the programmer to avoid access beyond the limits of a container. If by accident element xx[xx.ssiizzee()+77]
of some container x is accessed, then unpredictable – and usually bad – things happen. Using a
range-checked vveeccttoorr, such as V
Veecc (§3.7.1), helps in some cases. More cases can be handled by
checking every access through an iterator.
To achieve this degree of checking without placing a serious notational burden on the programmer, we need checked iterators and a convenient way of attaching them to containers. To make a
C
Chheecckkeedd__iitteerr, we need a container and an iterator into that container. As for binders (§18.4.4.1),
inserters (§19.2.4), etc., I provide functions for making a C
Chheecckkeedd__iitteerr:
tteem
mppllaattee C
Chheecckkeedd__iitteerr m
maakkee__cchheecckkeedd(C
Coonntt& cc, IItteerr ii)
{
rreettuurrnn C
Chheecckkeedd__iitteerr(cc,ii);
}
tteem
mppllaattee C
Chheecckkeedd__iitteerr m
maakkee__cchheecckkeedd(C
Coonntt& cc)
{
rreettuurrnn C
Chheecckkeedd__iitteerr(cc,cc.bbeeggiinn());
}

These functions offer the notational convenience of deducing the types from arguments rather than
stating those types explicitly. For example:

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

562

Iterators and Allocators

Chapter 19

vvooiidd ff(vveeccttoorr& vv, ccoonnsstt vveeccttoorr& vvcc)
{
ttyyppeeddeeff C
Chheecckkeedd__iitteerr C
CII;
C
CII pp11 = m
maakkee__cchheecckkeedd(vv,vv.bbeeggiinn()+33);
C
CII pp22 = m
maakkee__cchheecckkeedd(vv);
// by default: point to first element
ttyyppeeddeeff C
Chheecckkeedd__iitteerr C
CIIC
C;
C
CIIC
C pp33 = m
maakkee__cchheecckkeedd(vvcc,vvcc.bbeeggiinn()+33);
C
CIIC
C pp44 = m
maakkee__cchheecckkeedd(vvcc);
ccoonnsstt vveeccttoorr& vvvv = vv;
C
CIIC
C pp55 = m
maakkee__cchheecckkeedd(vv,vvvv.bbeeggiinn());
}

By default, ccoonnsstt containers have ccoonnsstt iterators, so their C
Chheecckkeedd__iitteerrs must also be constant iterators. The iterator pp55 shows one way of getting a ccoonnsstt iterator for a non-ccoonnsstt iterator.
This demonstrates why C
Chheecckkeedd__iitteerr needs two template parameters: one for the container type
and one to express the ccoonnsstt/non-ccoonnsstt distinction.
The names of these C
Chheecckkeedd__iitteerr types become fairly long and unwieldy, but that doesn’t matter when iterators are used as arguments to a generic algorithm. For example:
tteem
mppllaattee vvooiidd m
myyssoorrtt(IItteerr ffiirrsstt, IItteerr llaasstt);
vvooiidd ff(vveeccttoorr& cc)
{
ttrryy {
m
myyssoorrtt(m
maakkee__cchheecckkeedd(cc), m
maakkee__cchheecckkeedd(cc,cc.eenndd());
}
ccaattcchh (oouutt__ooff__bboouunnddss) {
cceerrrrbbeeggiinn(); pppp!=cc->eenndd(); ++pppp) iiff (pppp == pp) rreettuurrnn;
tthhrroow
w oouutt__ooff__bboouunnddss()
}
ffrriieenndd bbooooll ooppeerraattoorr==(ccoonnsstt C
Chheecckkeedd__iitteerr& ii, ccoonnsstt C
Chheecckkeedd__iitteerr& jj)
{
rreettuurrnn ii.cc==jj.cc && ii.ccuurrrr==jj.ccuurrrr;
}
// no default initializer.
// use default copy constructor and copy assignment.
C
Chheecckkeedd__iitteerr(C
Coonntt& xx, IItteerr pp) : cc(&xx), ccuurrrr(pp) { vvaalliidd(pp); }
rreeffeerreennccee__ttyyppee ooppeerraattoorr*()
{
iiff (ccuurrrr==cc->eenndd()) tthhrroow
w oouutt__ooff__bboouunnddss();
rreettuurrnn *ccuurrrr;
}
ppooiinntteerr__ttyyppee ooppeerraattoorr->()
{
rreettuurrnn &*ccuurrrr;
}

// checked by *

C
Chheecckkeedd__iitteerr ooppeerraattoorr+(D
Diisstt dd)
// for random-access iterators only
{
iiff (cc->eenndd()-ccuurrrreenndd()-ccuurrrreenndd()) tthhrroow
w oouutt__ooff__bboouunnddss();
++ccuurrrr;
rreettuurrnn *tthhiiss;
}

The C++ Programming Language, Third Edition by Bjarne Stroustrup. Copyright ©1997 by AT&T.
Published by Addison Wesley Longman, Inc. ISBN 0-201-88954-4. All rights reserved.

564

Iterators and Allocators

C
Chheecckkeedd__iitteerr ooppeerraattoorr++(iinntt)
{
C
Chheecckkeedd__iitteerr ttm
mpp = *tthhiiss;
++*tthhiiss;
rreettuurrnn ttm
mpp;
}

Chapter 19

// postfix ++

// checked by prefix ++

C
Chheecckkeedd__iitteerr& ooppeerraattoorr--()
// prefix -{
iiff (ccuurrrr == cc->bbeeggiinn()) tthhrroow
w oouutt__ooff__bboouunnddss();
--ccuurrrr;
rreettuurrnn *tthhiiss;
}
C
Chheecckkeedd__iitteerr ooppeerraattoorr--(iinntt)
{
C
Chheecckkeedd__iitteerr ttm
mpp = *tthhiiss;
--*tthhiiss;
rreettuurrnn ttm
mpp;
}

// postfix --

// checked by prefix --

ddiiffffeerreennccee__ttyyppee iinnddeexx() { rreettuurrnn ccuurrrr-cc.bbeeggiinn(); } // random-access only
IItteerr uunncchheecckkeedd() { rreettuurrnn ccuurrrr; }
// +, -, < , etc. (§19.6[6])
};

AC
Chheecckkeedd__iitteerr can be initialized only for a particular iterator pointing into a particular container.
In a full-blown implementation, a more efficient version of vvaalliidd() should be provided for
random-access iterators (§19.6[6]). Once a C
Chheecckkeedd__iitteerr is initialized, every operation that
changes its position is checked to make sure the iterator still points into the container. An attempt
to make the iterator point outside the container causes an oouutt__ooff__bboouunnddss exception to be thrown.
For example:
vvooiidd ff(lliisstt& llss)
{
iinntt ccoouunntt = 00;
ttrryy {
C
Chheecckkeedd__iitteerr< lliisstt > pp(llss,llss.bbeeggiinn());
w
whhiillee (ttrruuee) {
++pp;
// sooner or later this will reach the end
++ccoouunntt;
}
}
ccaattcchh(oouutt__ooff__bboouunnddss) {
ccoouutt