Using The STL The C++ Standard Template Library (PDFDrive)
Using The STL The C++ Standard Template Library (PDFDrive)
Second Edition
Springer Science+Business Media, LLC
Robert Robson
Second Edition
With 36 Illustrations
, Springer
Robert Robson
2645 Battleford Road, #1104
Mississauga, Ontario L5N 3R8
Canada
[email protected]
987654 32 1
ISBN 978-0-387-98857-3
Preface to the Second Edition
A lot has happened since the first edition of this book was written. When the
first edition was prepared, there was only one version of the Standard Template
Library (STL) available-the Hewlett-Packard version. Since then, several other
versions have appeared from major compiler and library vendors. This is in an
effort to conform to the recent International Standards Organization/International
Electrotechnical Commission (ISO/IEC) C++ standards, which define the STL
as part of the Standard C++ Library.
As expected, the STL is becoming widely available and an accepted part of
C++ program development. This is good. Unfortunately, the proliferation of
implementations makes it difficult to exactly define the STL. We now have mul-
tiple implementations, many of which are slightly incompatible with one another.
The reasons for this are largely due to different capabilities of the compilers on
which they are implemented. Many compilers do not implement the most recent
features of the language since production of standards often precedes conform-
ing implementations by many months. This will improve over time as the com-
pilers add the necessary capabilities to support the full STL.
In attempting to revise a book on the STL, I was faced with the question of
which definition to use. When there was only a single implementation, the an-
swer was obvious. With several implementations differing in slight ways, there
is the possibility of describing the most commercially successful version, de-
scribing an idealized version, trying to describe all of the versions, or describing
the standard. Describing the most commercially successful version would do a
disservice to those running other versions. Describing all versions is just not
practical since I do not have access to all of the hardware necessary to run some
of the versions out there. Therefore, I decided to describe the STL as defined by
the most recent ISO/IEC C++ standard of September 1998. Although I have yet
to find an implementation that conforms to this standard, most of the implemen-
tations come very close--close enough that the casual user might not even notice
vi Preface to the Second Edition
the differences. Furthermore, the compiler and library vendors are working on
implementations that fully conform to the standard. Thus, by the time you read
this, a conforming implementation might be available. Regardless, this is the
standard that all vendors will be striving to meet and is the safest implementation
to describe.
This book also describes extensions provided by some of the popular imple-
mentations. I have not attempted to be rigorous in this, as I merely want to pro-
vide you with a glimpse of what is possible and how future development of the
STL might proceed. I want to introduce these extensions to those of you who
might be using those libraries. The space devoted to these discussions is mini-
mal, and it is clearly indicated that such capabilities are not part of the C++
standard.
All languages evolve, and C++ is no different. Thus, any book describing a
language can be no more than a snapshot of the language as it is at some point in
time. The C++ standard will change, and the definition of the STL will likely
change with it. Fortunately, most of the concepts and basic structure of the lan-
guage and libraries will remain the same. Therefore, I have hopes that the infor-
mation in this book will be useful for a period of years.
Thanks are due to the readers who provided comments on the first edition. I
have attempted to address their comments in an effort to correct deficiencies in
the first edition. My thanks also go to Michelle French, who was an enormous
help in editing the manuscript for the second edition.
Robert Robson
Mississauga, Ontario, Canada
1999
Preface to the First Edition
Programming languages evolve in steps. They began with weakly typed lan-
guages like FORTRAN and progressed to strongly typed languages like
PASCAL. This had the positive effect of moving the detection of many pro-
gramming errors from run time to compile time. This had the negative effect of
limiting the generality of functions since they were now bound to specific data
types. This virtually eliminated the writing of reusable software components.
The result was that programmers had to reimplement common algorithms and
data structures over and over.
Newer languages, such as C++, provide a way to decouple algorithms and
data structures from the data types upon which they operate. C++ provides this
capability via the template mechanism. Suddenly, it became possible to write
generic algorithms and data structures that could be reused. In effect, this pro-
vides debugged software components that can be combined to form programs
much faster than by reimplementing the components each time they are needed.
At the same time that programming languages were evolving, computer hard-
ware was becoming incredibly inexpensive compared to programmers' salaries.
This provided a strong incentive to reduce costs by increasing programmer pro-
ductivity. Software reuse was seen as one way to increase programmer pro-
ductivity.
The drive to increase programmer productivity was initially met by the intro-
duction of reusable components by compiler vendors and other software compa-
nies. While this was a major step forward, it was flawed by introducing multiple
libraries that addressed the problem in different ways. The result was that com-
ponent reuse meant that a programmer might need to learn several such libraries.
viii Preface to the First Edition
The Standard Template Library (STL) was introduced after several years of
studying how a library of reusable components should be designed. As a result,
it introduces several new concepts that make the library easier to use and allow
the generic algorithms to be applied to data stored in arrays as well as the con-
tainers provided by the STL.
The STL has been accepted as a standard by the American National Stan-
dards Institute (ANSI) committee for the standardization of the C++ language.
This means that it must be provided by all C++ compilers, just like common
functions such as strlen ( ). The result is a single, well-defined library of re-
usable components that is available to all programmers regardless of the com-
piler or platform they use.
This book introduces the algorithms and data structures that comprise the
STL and the philosophy behind their creation. An understanding of the philoso-
phy is crucial since it allows you to see the underlying structure and regularity in
the library. An understanding of this regularity reduces the amount of material
that you must remember to use the STL effectively.
This book began as a series of notes on the use of the STL for a program-
ming project in which I was involved. As the magnitude of effort and the re-
quired detail became apparent, turning it into a book was the natural thing to do.
The resulting book not only describes the components of the STL and the
philosophy behind them, but it shows how you can use them. This is done by
pointing out some of the pitfalls in the usage of the STL and by showing exam-
ples of real-world problems being solved with the STL. An explanation of many
of the algorithms implemented by the STL is provided for those readers not fa-
miliar with the underlying concepts.
While this book is aimed primarily at professional programmers, it is not re-
stricted to this audience. It is suitable for software managers who need a high-
level understanding of the STL as well as students who wish to expand their pro-
gramming knowledge and skills. A table at the end of the introduction shows
which chapters are most appropriate to each type of reader.
I believe that the STL will become an important component in the Standard
C++ Library and that it will be vital for programmers to have a good working
knowledge of the STL. The result will be decreased tedium in the production of
software, higher quality, and increased productivity.
Preface to the First Edition ix
I would like to thank the people who read initial versions of this book espe-
cially Heather Collicutt and Brigitte Bonert. I also wish to thank Ross Judd of
Atomic Energy of Canada, Ltd., who had the foresight and interest to allow me
the time to investigate the STL and write this book. Final thanks go to all of the
people at Springer-Verlag who made this book possible.
I wish you well in your programming endeavors and hope that this book
helps you achieve your goals.
Robert Robson
Mississauga, Ontario, Canada
1997
Contents
Preface to the Second Edition ..... .................. .................... ...... ..... ....... ... v
Preface to the First Edition .................................................................... vii
1 Introduction
1.1 What Is the STL? ................................................................... 1
1.2 History .................................................................................... 3
1.3 STL Components ............ .................. ...... .............. ................. 3
1.4 Generic Algorithms ................................................................ 4
1.4.1 C++ Templates ........................................................... 7
1.5 Iterators ................................................................................... 10
1.6 Standard Exceptions ............................................................... 13
1.7 Complexity .. .............. ......... ...... ......... .................... ......... ........ 14
1. 7.1 Analyzing Complexity .. ........ ...... ............ ................... 22
1.8 Thread Safety ......................................................................... 23
1.9 Namespaces ............................................................................ 25
1.10 Overview of This Book ......................................................... 25
2 Iterators 29
2.1 Introduction 29
2.2 Pointers as Iterators ............................................................... . 29
2.3 Iterator Classes ...................................................................... . 34
2.3.1 Input Iterators ........................................................... .. 39
2.3.2 Output Iterators ........................................................ .. 40
2.3.3 Forward Iterators ....................................................... . 41
2.3.4 Bidirectional Iterators ................................................. 41
2.3.5 Random Access Iterators ........................................... 42
2.4 Using Iterators ........................................................................ 42
2.4.1 Stream Iterators ...... .................... .............. .................. 43
2.4.2 Forward Iterators ...... ...................... ...................... ...... 46
xii Contents
4 Sequence Algorithms 73
4.1 Introduction ............................................................................ 73
4.2 Preliminaries ........................................................................... 73
4.2.1 Pairs .......................................................................... 77
4.3 Nonmutating Sequence Algorithms ....................................... 79
4.3.1 Counting ................................................................... 80
4.3.2 Finding ...................................................................... 83
4.3.3 Finding Members ..................................................... 83
4.3.4 Finding Adjacent Values .......................................... 85
4.3.5 ForEach ..................................................................... 86
4.3.6 Mismatching Values ................................................. 90
4.3.7 Sequence Equality .................................................... 92
4.3.8 Searching .................................................................. 94
4.4 Mutating Sequence Algorithms ............................................. 97
4.4.1 Copy ......................................................................... 97
4.4.2 Swapping .................................................................. 99
4.4.3 Filling ....................................................................... 101
4.4.4 Generate .................................................................... 102
4.4.5 Replace ..................................................................... 104
4.4.6 Transform ................................................................. 106
4.4.7 Remove ..................................................................... 108
4.4.8 Unique ...................................................................... 110
4.4.9 Reverse ..................................................................... 112
4.4.10 Rotate ........................................................................ 113
4.4.11 RandomShuffle ....................................................... 115
4.4.12 Partitioning ............................................................... 116
Contents Xlll
6 Generalized Numeric Algorithms ... ... ... ...... ........ ... ..... ....... ...... ... ... 165
6.1 Introduction ... ..... ......... ... ...... ... ...... ... ... ..... ...... ..... ... ....... ... ... ... 165
6.2 Accumulation .. ..... ... ...... ... ... ...... ... ..... ... ... ..... ... ... ... ... ...... .... .... 165
6.3 Inner Product .......................................................................... 167
6.4 Partial Sum ........ ... ... ... ..... ......... .... ..... ... ... ..... ... ... ... ... ...... ... ..... 169
6.5 Adjacent Difference ............................................................... 170
6.6 Numeric Arrays ...... ... ...... ... ... ... ... ... ........ ... ... ...... ... ......... ... ..... 172
6.6.1 Subscripting ..... ...... ... ... ... ... ... ... ..... ... ... ..... ... ...... .... ...... 173
6.6.2 Methods and Functions .............................................. 179
8 Associative Containers .... .... ... ......... ............. ......... ... ...... ... ...... ... ..... 249
8.1 Introduction 249
8.2 Associative Container Operations ........................................ .. 250
8.3 Sets ........................................................................................ . 255
8.4 Multisets ................................................................................ . 263
8.5 Maps ...................................................................................... . 263
8.5.1 Multimaps .................................................................. . 267
8.6 Associative Container Implementation ................................. . 267
8.6.1 Trees .......................................................................... . 268
8.6.2 Hash Tables ............................................................... . 277
8.7 Hash Table Implementations ................................................ . 280
8.8 Container Selection ............................................................... . 284
9 Adaptors 285
9.1 Introduction ............................................................................ 285
9.2 Container Adaptors ................................................................ 286
9.3 The Stack ................................................................................ 286
9.4 Queues ................................................................................... . 293
9.5 Priority Queues ...................................................................... . 295
9.6 Iterator Adaptors ................................................................... . 298
9.6.1 Reverse Iterators ........................................................ . 299
9.6.2 Insert Iterators 300
9.7 Function Adaptors ................................................................. . 303
9.7.1 Not1 ........................................................................... . 303
9.7.2 Not2 ........................................................................... . 304
9.7.3 Pointers to Functions and Methods .......................... . 306
9.7.4 ptr_fun ....................................................................... . 306
References 585
Index 589
1
Introduction
1.2 History
The main authors of the Standard Template Library are Alexander Stepanov and
Meng Lee, both of whom work for Hewlett-Packard. Stepanov and David
Musser of the Rensselaer Polytechnic Institute began working on generic pro-
gramming in the early 1980s, implementing the ideas in Ada and Scheme. When
C++ adopted templates, they began a C++ implementation of the library that had
been developed in Ada. Since the original C++ implementation of templates did
not support the capabilities required, Stepanov had discussions with Bjarne
Stroustrop and the C++ language was extended to provide the necessary
capabilities.
Stroustrop suggested to Stepanov that he present his ideas to the ANSI com-
mittee for the standardization of C++. This was done at the June 1993 meeting
of the committee and, to Stepanov's surprise, they agreed to adopt it as an exten-
sion to the C++ library.
The original version of the STL was produced by Stepanov and Lee at
Hewlett-Packard and released in October 1995. The standardization committee
has made revisions to streamline some of the algorithms and to increase the regu-
larity of the application programming interface. They have also added important
classes such as the string and the valarray. The version of the STL used
in this book corresponds to International Standard ISO/IEC 14882, Program-
ming Languages-C++, of September 1998.
types since there are no other data types to which they are applicable. Therefore,
we can conclude that to produce generic algorithms, we must be able to separate
the algorithm from the data representation that it is manipulating.
A simple example serves to illustrate this point. Consider the case of per-
forming a linear search of an array looking for the first occurrence of a specific
value. The code for searching an array of integers might look like Listing 1.1:
The algorithm is fairly straightforward and works the way we want it to work.
The problem is that when we have to search an array of strings looking for a spe-
cific string, we find that we must rewrite the algorithm, as in Listing 1.2:
return NULLi
}
These two functions have both great similarities and great differences. Ex-
amining them, we find that the algorithm for the search is identical in each case.
6 1: Introduction
The differences lie in the data types and the operations that are performed on
them. The different data types require different declarations, necessitating a re-
write of the function. The comparison of values is also different. For integers,
we can simply use opera tor==, but strings require the use of strcmp ( ).
To make the linear search algorithm generic, we must separate the represen-
tation of the type and the operations on that type from the algorithm itself. One
approach to this is to use a generic pointer to pass the data and pass function
pointers to perform the necessary operations. The linear search implemented us-
ing this technique is shown in Listing 1.3:
ptr = (char*)array;
endPoint = ptr + (size * elemSize);
for(;ptr 1= endPoint;ptr+=elemSize)
if( 0 == comp(ptr, value» return ptr;
}
return NULL;
}
Making the function generic requires a little more work than originally
planned since the function must know the size of each element in the array to be
able to iterate through it correctly. There is no doubt that this technique works,
but as it is used, several problems become apparent:
• The parameter list is longer, making it more bothersome to type and more
prone to error.
• The user must correctly specify the size of the elements in the array, an
implementation detail that is best left to the compiler.
• The use of void* as a generic pointer is not type-safe. The program-
mer can inadvertently pass an array of one type, a value of another
type, and assign the result to a third type. The compiler will happily
Generic Algorithms 7
generate code for this and never notice that anything is amiss. This by-
passes all the type checking provided by the language and creates a multi-
tude of potential errors.
A much better way to isolate the data type from the algorithm is to use the
template capabilities of C++. Revising our linear search to use templates, we get
the Listing 1.4:
return NULL;
}
This satisfies the requirements for genericity while not circumventing the
type system of the language. Thus, it is a safer, easier-to-use solution to the
problem than that provided by using void * to gain independence from the data
type. We conclude that the best approach to writing generic functions in C++ is
to employ the template facility.
The C++ template facility provides a way to abstract the data type on which an
algorithm operates from the algorithm itself. This separation of type from algo-
rithm allows the creation of generic code that can be reused in a wide variety of
situations. This is the true meaning of code reuse, not the trivial reuse of code
that is achieved by inheriting methods from a base class. Although this is reus-
ing existing code, it is limited to a single class hierarchy and could be achieved
by using plain old structures and functions. The reuse of existing algorithms
with different data types will save a lot more effort in the long run than the effort
saved by inheritance.
8 1: Introduction
private:
T storage[VECTOR_SIZE];
};
A class like this allows you to provide operators that can allocate more stor-
age if the vector becomes full or check the validity of subscripts rather than hav-
ing invalid subscripts result in a run-time error. The trouble with designing a
class like this is that we need to know what type of values are to be stored in the
vector. Unfortunately, this cannot be known beforehand. We are left with these
choices:
• Create a version of the vector class for each type that will be stored in the
vector.
• Use a void * pointer to store the location of the physical storage.
• Use a template to abstract the type that is to be stored in the vector.
The first solution requires writing a lot of repetitious code and, even if this is
done, there will always be a new type that someone will want to store in a vector
that will require writing yet another version of the code. The second solution,
the use of a void*, subverts the type-checking of the language and opens the
user to a plethora of new errors. The onus for ensuring that all the little details
Generic Algorithms 9
are correct is shifted from the compiler onto the programmer-not a desirable
situation. The third solution is the only one that makes sense. It allows the type
to be changed without having to rewrite the code, while maintaining the strong
type checking of the language.
The first line of the declaration states that this is a template and will be
parameterized by a single type T. (The word class is required and does not im-
ply that the type T can only be a class.) Within the class declaration itself, any
occurrence of the type T is replaced by the actual type specified as a parameter
when the class is created. For example, to create a vector of integers we would
write this:
vector<int> intVector;
This is similar to supplying the actual parameters in a function call. When
this object is created, all the instances of type T in the class will be replaced by
type into The way templates are implemented by the compiler is very different
than the way parameters are passed to a function. Rather than passing values,
the compiler must change types, which must then go through the normal type-
checking mechanism of the compiler. The simplest way to do this is to have the
compiler write a new version of the class, which then goes through the normal
type-checking and compilation process.
At this point, it should be obvious why these are called templates. Although
it appeared as if we wrote a vector class, in reality we wrote a template from
which such a class could be created once the type information was known. The
code for an instance of the vector class for a specific type was actually written by
the compiler! The advantage of this is that effort is shifted from the programmer
to the compiler without altering the run-time efficiency.
It can be argued that, if a great number of types are used to instantiate a tem-
plate, the compiler will invisibly generate a lot of code that will inflate the size
of the resulting executable. Although this is true, the severity of the problem can
be reduced by using smaller classes and moving functionality out of methods and
into nontemplate functions whenever possible. The alternative is to achieve a
greater degree of physical code reuse by using void* to make the same code
work with any type. Given the size of modem computer memories versus the
cost of correcting programming mistakes created by the use of void*, it makes
sense to sacrifice memory to obtain more reliable software.
One last point to note is that the types passed to a template need not always
be types-values can be used as well. This facility is provided mainly to allow
programmers to alter the size of data to be allocated in a class by passing the size
as a parameter to the template. In the vector class, we have to know how big to
10 1: Introduction
make the underlying array used to store the actual values. We could modify our
definition of the vector to take advantage of this, as shown in Listing 1.6:
private:
T storage[size];
} ;
Listing 1.6 - A Simple Vector Class with the Size as a Template Parameter
The same effect could be achieved by passing the size as a parameter to the
constructor, but that would imply that the space allocation would have to be
done at run-time. This could make the use of a class such as this less efficient
than a built-in array and would discourage programmers from using such classes.
The STL depends heavily on the template facility of C++ to achieve its goal
of providing generic algorithms. This takes us a long way toward achieving our
goal of reusability, but as we examine the problem more closely, we find there
are still improvements to be made.
What happens if we try to use the linear search function, developed previ-
ously, on a linked list? The answer is that it fails miserably since the current im-
plementation assumes that the data are stored in an array in contiguous storage.
We need to be able to generalize the addressing of the elements in the data struc-
ture being searched. An interesting approach to this problem is the one taken by
the STL, which generalizes the concept of the pointer so that it can be used on
any kind of container.
1.5 Iterators
One of the most common operations to perform on a container data structure is
to traverse all or some of the elements stored in the data structure. Traversal is
defined as visiting all or some of the elements in the container in a predefined or-
der. An object that can return a reference to each of the elements in a container
in tum is called an iterator.
Iterators 11
Our previous version of the linear search algorithm made use of such an it-
erator-the pointer. It used the address of the beginning of the array to find the
first element and added the size of the array to the address of the beginning to
find the address of the element just past the end. To advance from one element
to the next, it simply incremented the value of the pointer by the size of the ele-
ments in the array.
We can rewrite the linear search algorithm once again so that the parameters
are expressed in terms of iterators, as shown in Listing 1.7. Now, the function
will accept a pointer to the first element it will search and a pointer to the ele-
ment just after the last one it will search.
return NULL;
Let us examine the properties of our iterator. This function used the follow-
ing operators provided by the pointer:
• opera tor 1=
• opera tor++
• operator*
This is a very simple algorithm. If we examine more complex algorithms, we
find that further operations are required to use a pointer as an iterator. Ulti-
mately, an iterator must support all the operations that can be performed on a
pointer. These operations are summarized for the iterators x and y and the inte-
ger n:
12 I: Introduction
x++ X + n x - y x > y *x
++x x - n x -- y x <= y x = y
x- - x += n x 1= y x >= y
- -x x -= n x < y x[n]
1.7 Complexity
The designers of the STL realized the importance of the efficiency of algorithms.
They also realized that if the STL were to be widely adopted, the algorithms it
offered would have to be as efficient as those programmers could code by hand.
The approach they adopted was to select the most efficient algorithm they could
find for each operation. Nevertheless, some compromises had to be made to
yield a library that was truly generic. Furthermore, there is almost never a single
way to solve any problem, so they decided to state the efficiency of each algo-
rithm so that the programmers would have a basis for deciding among
Complexity 15
actual
speed
speed
a n
n log n n log n n2 n3
10 3.32 33.22 100 1,000
20 4.32 86.44 400 8,000
30 4.91 147.21 900 27,000
40 5.32 212.88 1,600 64,000
50 5.64 282.19 2,500 125,000
60 5.91 354.41 3,600 216,000
70 6.13 429.05 4,900 343,000
80 6.32 505.75 6,400 512,000
90 6.49 584.27 8,100 729,000
100 6.64 664.39 10,000 1,000,000
500 8.97 4,482.89 250,000 125,000,000
1,000 9.97 9,965.78 1,000,000 1,000,000,000
2,000 10.97 21,931.57 4,000,000 8,000,000,000
3,000 11.55 34,652.24 9,000,000 27,000,000,000
4,000 11.97 47,863.14 16,000,000 64,000,000,000
5,000 12.29 61,438.56 25,000,000 125,000,000,000
10,000 13.29 132,877.12 100,000,000 1,000,000,000,000
constant of two. Therefore, they started to consider just the order of an algorith-
m-the function with the proportionality constant removed.
Scientists also observed that the functions were true for nontrivial values of n
only. Many algorithms exhibited a different speed for small values of n, usually
less than five. Thus, the definition was modified to state that the function had to
hold only for values of n greater than some small value. This did not create a
Complexity 17
10
5V-
1500M
l000M
500M
10K
5K
1500K
l000K
500K
problem since most algorithms handled trivial amounts of data so quickly that no
one was really concerned about their speed.
It proved far more difficult to find a function to describe the exact speed of
some algorithms than others. To simplify the problem, scientsits changed the
definition once again to state that the speed could be expressed as a function that
formed a minimal upper bound on the actual speed. This means that it is permis-
sible to use a function that is very close to the actual speed-but always greater
than it-for nontrivial values of n. This can be seen in the graph of computation
time versus n (Figure 1.1).
Experimentally obtained results will not always yield a perfectly smooth
curve due to imprecision in timing and a small amount of time taken by periodic
activities such as flushing I/O buffers. Notice that the bounding function is in-
deed greater than or equal to the experimental results for nontrivial values of n.
At very low values of n, the two curves may cross, as shown above.
A notation was developed to express this bounding function that is called big
Oh notation because it is denoted by an upper case letter O. Thus, an algorithm
whose speed was found to be proportional to n would have its speed expressed
as O(n), and one proportional to n 2 would be expressed as O(n 2 ).
Although it is fine to talk about these functions in the abstract, most people
need to see some solid figures to truly understand what it all means. Table 1.1
shows values of n and the values of common functions of n.
The values of the logarithmic functions are actually base 2 logarithms. This
might seem to be a strange base to use, but it makes a lot of sense when we con-
sider the nature of computer algorithms. The algorithms that exhibit logarithmic
Complexity 19
2600
2400
D log n
2200 0 nlog n
n2
2000
1800
1600
1400
1200
1000
800
600
400
200
10 50
n
times typically function by splitting their data in half repeatedly until many small
groups of data (often single data units) are obtained. The algorithm then per-
forms calculations on the smaller amounts of data and recombines the smaller re-
sults to yield the final result. This is called the divide-and-conquer approach and
can be much faster than performing calculations on all the data at once.
To calculate the speed of such algorithms, we are faced with the problem of
how many times a value n can be cut in half. Taking a number like 8, we see
that it can be divided by 2 once to yield 4, which can be divided to yield 2,
which can be divided to yield 1. That is a total of three times that 8 can be di-
vided by 2. This is the same as asking how many times 2 can be multiplied by
itself to yield the value n, which is the exact definition of a base 2 logarithm!
Although other bases could be used, base 2 gives nice round numbers for the
powers of 2 that are commonly encountered in computer science.
The point to notice from Table 1.1 is that as n increases, log2n increases
slowly, nlog 2n increases much faster, n 2 increases faster still, and n 3 goes up
astronomically. The raw figures do not tell the whole story, however-it is also
useful to examine the shape of the curves to see how the rate of increase changes
with increasing values of n. The series of charts in Figures 1.2 to 1.5 show func-
tions of n for the range 50 ... 1000.
Notice the differing shapes of the curves as the value of n changes. In the
case of log n, the slope of the curve actually becomes more gentle as the value of
n increases. This implies that algorithms whose speed is proportional to a loga-
rithmic function can scale up to higher values of n very well. In fact, the value
of log n is always less than the value of n itself.
All of the other curves are concave upwards indicating that the speed of the
algorithm decreases faster and faster as the value of n increases. As the order in-
creases, the curve becomes more pronounced, indicating that the situation is
growing worse.
Let's look at one final chart where the functions are shown on the same set of
axes (Figure 1.6).
The function n 3 has been omitted since it would make the other functions
look like horizontal lines. Note how fast n 2 increases compared to the other
functions. This should serve as a demonstration of the amount of time it takes
for an n 2 algorithm to execute. Avoid such algorithms whenever possible, par-
ticularly when nontrivial amounts of data are involved.
There is one other common speed an algorithm can have--constant. This is
for an algorithm that takes the same amount of time regardless of the amount of
Complexity 21
data being processed. Although such algorithms are rare in practice, constant is
often used to indicate the speed of small operations such as multiplying two val-
ues together. The notation for an algorithm that takes constant time is 0(1).
The notion of complexity can also be applied to the amount of memory an al-
gorithm requires. Many algorithms require no more memory than that needed to
store the original data, whereas others require extra storage for intermediate re-
sults. Most algorithms will state their memory requirements as constant, mean-
ing that no additional memory is required.
Before you go off to select algorithms based on their performance, you
should have some idea of what the best speeds are for some common operations.
This is summarized in Table 1.2.
This table demonstrates that if you have to sort some values, there is no ex-
cuse for using a sort that offers performance worse than O(n log n) since the STL
provides an easy-to-use function that has that performance. Second, if you have
some unsorted values and have to perform multiple searches of them, it might
well be worth it to sort them so that the search time will be reduced. If a great
number of searches are being performed, then a hash table could be used, al-
though this particular data structure usually needs to be tuned to the particular
problem to offer its best performance.
Algorithmic complexity is not that difficult to comprehend, although it is
sometimes very difficult to calculate. Paying attention to the complexity of the
algorithms you use and how they are combined in your program will result in
programs that execute in a fraction of the time required by those where the algo-
rithms are selected in a haphazard fashion.
22 I: Introduction
int Sumprod(int n)
{
int result, k, ii
resulJ:. -=
0 i O(1}_ _ _ a 1 n'O(n) '"'
for(k=li k<=ni k++) { --~------------- .J O( n')
or(i=li ~ < = ki i++)-(~
res_u~ += k*ii~~·O(1) = O(n)
b
r~rn(result)i Of1...LJ c
Boxes have been drawn around the different portions of the code to break it
into logical sections. The top box (a) initializes the value of result to zero
and is independent of the value of n i so is O( 1). The same is true of the bottom
box (c) which returns the result. The center box (d) contains the multiplication,
which is also of 0(1). It is, however, in a loop that goes from 1 to k. This loop
is executed a maximum of n times since that is the largest value that k can be as-
signed by the outer loop, and we always assume that a loop is executed the maxi-
mum number of times. This means that the inner loop has a complexity of O(n)
and the combined complexity of the algorithm in box d is O(n) x 0(1) = O(n)
since the inner loop contains the multiplication and we multiply complexities
when one is contained in another. The same logic is applied to calculate the
complexity of box b as O(n) x O(n) = 0(n2). Finally, three sequential algo-
rithms are combined by finding the maximum of their individual complexities,
max (0(1), 0(n2), 0(1» = 0(n2), the complexity of the complete algorithm.
The complexity of each of the STL algorithms is clearly stated so that you
can use the outlined techniques to calculate the overall complexity of your pro-
gram. Proper use of this technique will let you select the most efficient STL al-
gorithms and combine them in the most efficient manner.
first thread was interrupted before it could complete, leaving the container in an
inconsistent state.
The solution to this problem is to make all of the operations on a container
atomic. This means that the thread performing an operation on a container can-
not be interrupted until the operation is complete. This guarantees that when any
thread starts to perform an operation on a container, the container will be in a
consistent state.
Most operating systems provide some type of thread synchronization primi-
tives. Often, these take the form of a mutex, or some way to guarantee an area of
mutual exclusion so that only a single thread can be executing the code within
the area of mutual exclusion. The synchronization primitives vary from one im-
plementation to another, but they usually provide similar capabilities.
C++ was designed for performance. Since the locking required to implement
mutual exclusion can be expensive, its use conflicts with the design goals of the
language. Further, C++ does not define thread synchronization primitives within
the language, so there are no thread operations that could be used that would be
portable. The standard does not mandate that the STL containers be thread-safe,
leaving this to the library implementor.
Most of the STL implementations define thread-safety as not using noncon-
stant static data in the containers and not using global data. The Hewlett-
Packard (HP) implementation doesn't meet these criteria since both the list and
deque share common storage among instances. The Silicon Graphics Inc. (SGI)
implementation claims that there is no nonstatic data shared among instances of
their containers. SGI further claims that its allocators are thread-safe so that the
allocator itself will not cause a problem with threads. Other commercial imple-
mentations also claim thread-safety [IG98, RI99].
These thread-safe libraries are ready to be used in conjunction with threads,
but they require that thread synchronization primitives be used. The simplest ap-
proach to using threads safely is to derive a thread-safe class from an existing
thread-safe STL container. This is done by placing the operations that access the
container in areas of mutual exclusion. No other operation will be able to start
until the operation accessing the container completes, ensuring mutual exclusion.
One further point is that for some higher-level operations on containers, the
use of thread-safe container methods will not be sufficient to ensure atomicity.
As an example, consider the case of finding a reference to an object in a con-
tainer and using the reference to modify the object. There must be no interrup-
tion between these two operations. Otherwise, there is the possibility that the
reference could be invalidated by an operation performed by another thread.
Thread Safety 25
Operations such as this must lock out other operations on the container until they
complete.
If you are not sure whether the STL implementation you are using is thread-
safe, check the vendor's documentation to see if the implementation is free of
nonconstant shared data and global data. You should also check the allocator to
ensure that it is thread-safe.
1.9 Namespaces
The c++ standard mandates that all STL data structures and algorithms be
placed in the namespace std. This means that, in addition to including the ap-
propriate header file, you must qualify the names of STL components or employ
the using statement to merge all components in the std namespace into the
global namespace. Further, the relational operator templates (eg, opera tor»
are in the namespace rel_ops within the namespace std.
Legend:
GV' should be read in depth
+ fly through or skim the topic
ill for reference only
2.1 Introduction
Virtually all of the STL algorithms and data structures make extensive use of it-
erators. Therefore, the programmer must have a good understanding of what an
iterator is and how it works. Fortunately, since iterators are a generalization of
pointers, they share many of the properties of pointers, making them relatively
easy to master for the average C programmer.
This chapter begins by using pointers as iterators, showing how they can be
used in a variety of STL algorithms, and noting the properties that pointers pos-
sess. It then goes on to introduce the different types of STL iterators and to il-
lustrate their use on simple container classes.
I recommend that you read this chapter before continuing on to further chap-
ters if you have no previous experience with the STL.
an apple, examine it, and put it back. Now, when you reach for the second apple
to check it for worms, you cannot tell which one you already checked. All the
apples look alike and the more you check, the worse the problem becomes. You
could use another basket to hold the apples you have checked, but baskets cost
money and that technique would increase the number of baskets you need. What
you really need is an automated tool that will reach into the basket for you, re-
trieve an apple, hand it to you, then put it back when you are done. This tool
would go through the apples in an organized fashion so that each apple was
handed to you exactly once. In the world of the fruit picker, no such tool exists;
in the world of computer programming, we have such a tool and it is called an
iterator.
Computer programmers use containers for much the same reason as do fruit
pickers-convenience. It is much easier to move a single object around and
keep track of it than to handle many smaller objects. Programmers, like fruit
pickers, want to access the contents of a container one at a time without duplica-
tion. For this reason, the concept of the iterator evolved.
The simplest type of container is the array. Programmers use arrays to hold
many different types of objects including predefined types such as integers, char-
acters, and floating point values as well as user-defined structures and even other
arrays. One common use of the array is to contain the characters that constitute a
string. Such a string can be declared and initialized:
char message[32];
strcpy(message, "Hello World");
This creates the string and assigns a value to it using the library function
strcpy ( ). For purposes of illustration, let's assume that we want to find the
number of characters in the string but prefer not to use the library function
strlen () and we wish to write our own. The code for the new function is
shown in Listing 2.1:
Although this is a very simple function, it requires the use of an iterator. The
iterator is the pointer to the string itself, str. This pointer has the same proper-
ties as our magic fruit iterator-it can retrieve every value in the container ex-
actly once. Each time the while test is executed, str is dereferenced using
operator* to obtain the character stored in that position of the array. Then,
the value of str is incremented to move it to the next character in the string.
This illustrates two of the operations that every iterator must possess-derefer-
encing and moving to the next object in the container.
If we try to write a function to reverse the order of the characters in a string,
we find that other operations are needed for the iterator, as shown in Listing 2.2:
len = mystrlen(str);
if(len < 2) return;
endPt = str + len - 1;
while(str < endPt) {
temp = *str;
*str = *endPt;
*endPt temp;
str++;
endPt-- ;
The reverse function requires several new operations. The initial calcula-
tion of the endPt requires that integer arithmetic be performed on an iterator.
The goal of this operation is to advance the iterator to the last character in the
string. For a character pointer, this is simple integer addition and subtraction.
For pointers to types longer than a single byte, it will require a multiplication of
the number to be added by the size of the type in the array. Thus, we see that
when the position of an iterator is adjusted, the actual operation that is performed
depends on the type that is being iterated. When we examine the iterators pro-
vided by the STL, we will see that the operation for advancing an iterator de-
pends on the nature of the container as well.
32 2: lterators
len mystrlen(str);
endPt = str + len;
result = find(str, endPt, value);
Pointers as lterators 33
The find () function takes three parameters. The first two parameters are
iterators that delimit the range to be searched. The first parameter references the
first character to be searched, and the second parameter references the location
immediately after the last character to be searched. This might seem like a
strange way to indicate where the string ends, but it is the standard way of indi-
cating ranges in the STL. Whenever an STL algorithm takes two iterators indi-
cating a range, the first iterator will refer to the start of the range, and the second
will refer to the position immediately after the end of the range. The third pa-
rameter to find ( ) is the value for which it is to search.
The find () function returns an iterator, in this case a pointer, referencing
the first occurrence of val ue in the indicated range. If the value does not occur
in the range, the function returns an iterator equal to the second parameter, the
past-the-end iterator for the range.
I decided that MyFind ( ) would return a pointer to the value, iffound, or
NULL, if it is not present. Notice how the iterator that is returned from find ( )
is compared with endPt to determine whether NULL should be returned.
It is also interesting to examine the calculation of endPt. The length of the
string is added to the address of the first character, yielding the location of the
string terminator one past the last character in the string. This satisfies the re-
quirements of a past-the-end iterator for the string.
We could also sort the characters in the string by using the sort function
from the STL. The sort function takes two parameters indicating the range of
elements to be sorted. As in all STL algorithms, the first iterator refers to the
first value in the range, and the second iterator refers to the location one past the
end of the range. The code in Listing 2.4 demonstrates how we could sort a
string:
strcpy(text, "abbracadabra");
34 2: Iterators
len = mystrlen(text);
endPt = text + len;
sort(text, endPt);
cout « text;
The use of iterators in the sort () function is identical to their use in the
find () function. In fact, all of the STL algorithms use iterators in the same
way, making the library consistent and easier to learn. The sort () function
produces an ascending sequence by default. How to get it to sort in other orders
will be examined later.
Have you started to wonder what the STL functions do with the iterators
passed to them? The answer is pretty much what you would expect. They per-
form the same operations on the iterators that we did in our functions
mystrlen ( ) and reverse ( ). They compare iterators, increment and decre-
ment them, and dereference them. As long as the iterators implement these op-
erations correctly, then the functions will be able to use the iterators and obtain
correct results.
The fact that pointers satisfy all the requirements of iterators is what permits
them to be used as iterators. Thus, all of the STL algorithms can be applied to
arrays just as easily as the STL container classes. This extends the utility of the
functions so that the same functions can be used on virtually any data structure in
a program. As a result, less code has to be written, resulting is shorter develop-
ment time and smaller executable programs. Once you get used to this, the idea
of one sort function for arrays and another for linked lists seems like a foolish
notion.
To get an idea of the problem, let's take a look at how we might implement
an iterator for a doubly linked list. First, we must design a doubly linked list
class, as shown in Listing 2.5:
private:
T data;
DoubleListNode *next, *prev;
};
node at the end of the list. This means that an empty list will contain two
nodes-the internal head pointer and the past-the-end node. Thus, the construc-
tor for the list would appear as in Listing 2.6:
The methods begin () and end () return iterators that reference the first
member of the list and the past-the-end node, respectively. If the list is empty,
beg in () will return an iterator equal to that returned by end () so that an
empty range is indicated. The code for these methods is shown in Listing 2.7:
The iterator must provide a constructor that can initialize an iterator given a
pointer to a node in the list. It must also offer the operations expected of an it-
erator. The first cut at writing the iterator is shown in Listing 2.8:
Iterator Classes 37
bidirectional
iterator
random access
iterator
The list iterator simply maintains a pointer to a node in the list. When we
want to increment it, it follows the pointer to the next member of the list. There
is no rocket science here.
The interesting question is what happens when we try to implement opera -
tor [] to allow direct access to list nodes. The postincrement followed the
pointer to the next list node, so positive subscripts could simply follow the
pointer chain the required number of times. Negative subscripts require that the
pointers to the previous nodes be followed. In both cases, the sequential nature
of the data structure requires that subscripting must be a linear time operation.
Lists, by their nature, provide only sequential access in constant time. Do we
really want to provide expensive linear-time iterators on a data structure that
does not lend itself to their support? This was the question that faced the STL
designers, and their answer was no. The solution they adopted was to break the
iterators into a series of categories where each category offered a different set of
operations. Each data structure would then support the iterator categories that it
could with reasonable efficiency.
The iterators are grouped into these categories:
• input iterators
• output iterators
• forward iterators
Iterator Classes 39
• bidirectional iterators
• random access iterators
The iterators. form a hierarchy in which iterators lower in the hierarchy have
all the operations of iterators higher in the hierarchy plus additional operations.
This hierarchy is depicted in Figure 2.1.
Iterators can also be constant or mutable, depending on whether dereferenc-
ing the iterator returns a constant reference or a nonconstant reference.
Not all iterators can be dereferenced. Those which can be dereferenced are
termed dereferencable. It is assumed that a past-the-end iterator is never
dereferencable.
An iterator is said to have a singular value if it does not reference any con-
tainer. An example of this is a pointer which is declared but does not have a
value assigned to it. The result of most expressions involving singular values is
undefined.
An iterator j is said to be reachable from an iterator i if a finite number of
applications of opera tor++ to i will result in j. This implies that i must be
less than j and that they must both reference the same container. The notation
[ i , j) denotes a range of iterators beginning with i and ending with the itera-
tor immediately before j. An iterator range [i, j) is valid only if j is reach-
able from i. An empty range is represented as [i, i ) .
Input iterators can move only in the forward direction and can be used only to re-
trieve values, not to output values. Input iterators a and b oftype II, referenc-
ing a type with a member m, provide these operations:
· I I b(a)
• b = a
• a++
• ++a
• *a (as an r-value only)
• a ->m (as an r-value only)
• *a++
• a b
• a 1= b
40 2: Iterators
Output iterators, like input iterators, can move only in the forward direction but
differ in that they can be dereferenced only to assign a value, not to retrieve a
value. Output iterators a and b of class or support the operations:
• or b(a)
• or b = a
• ++a
• a++
• *a (as an I-value only)
• *a++ (as an I-value only)
Like input iterators, output iterators are provided as a base class for output
stream iterators. Although you can dereference an output iterator to assign a
value to it, you cannot dereference it to retrieve a value from it. This is because
you cannot retrieve values from an output stream.
Iterator Classes 41
The forward iterators are designed to traverse containers to which values can be
written and from which values can be retrieved. The forward iterator relaxes
some of the restrictions of the input and output iterators but retains the restriction
that it can only move in the forward direction. Forward iterators a and b of class
FI referencing a type with a member m support these operations:
e FI a e a->m
e FI b(a) e *a++
e FI b = a ea = b
e a++ ea b
e ++a ea 1= b
e *a
Bidirectional iterators remove the restriction of the forward iterators that move-
ment is possible only in the forward direction. Bidirectional iterators a and b of
type B I support these operations:
e BI a e *a++
e BI b(a) ea = b
e BI b = a ea b
e a++ ea 1= b
e ++a e --a
e *a e a--
e a->m e *a--
42 2: Iterators
These are the same operations supported by forward iterators, plus the decre-
ment operators that allow the iterator to move backward as well as forward. All
other properties of forward and bidirectional iterators are the same.
The random access iterators remove the restriction that the iterator can be moved
only to the next or previous element in the container in a single operation. Ran-
dom access iterators a and b of class RI, plus the integer n, support these opera-
tions:
• RI a ·a b - n
• a
• RI b(a) ·a != b • a
- b
• RI b = a • --a • a [n]
• a++ • a -- • a != b
• *a • a += n •a > b
• a->m • a + n •a <= b
• *a++ • n
+ a • a >= b
-= n
• a = b • a
#include <iterator>
#include <iostream>
maine )
{
CopyToEnd(int_in,
istream_iterator<int, char>(), int_out);
cout « endl « "EOF" « endl;
return(O);
}
The function CopyToEnd ( ) is where all the action takes place. It is passed
an iterator indicating the start of the stream, an iterator that is one past the end of
the stream, and an iterator referring to the stream on which the output should be
placed. The body of the function contains a loop that dereferences the input it-
erator to obtain a value and then assigns this to the dereferenced output iterator.
Both iterators are then incremented, causing the current value to be printed on
the output stream and a new value to be read from the input stream. The loop
terminates when the input iterator becomes equal to the past-the-end iterator.
The function main () contains the declarations of the stream iterators and
the call to CopyToEnd ( ). The templates for the stream iterators require that
they be parameterized with the type of value to be input and the representation
used in the input stream. In this case, the stream will take in ints, which are
represented as chars in the input stream. This implies that a stream can contain
only a single type of value. A stream can contain all integers, all strings, or all
floating point values, but not a mixture.
The constructor for the input stream accepts a single parameter: a reference
to the stream it should handle. The constructor for the output stream takes a ref-
erence to an output stream as well as a string that will be printed on the stream
between every pair of values. In this case, a single space is provided that will
appear as a separator between every pair of integers on the output stream.
The call to CopyToEnd ( ) must provide an iterator that is one past the end
of the input stream. This value is obtained by constructing an istream_i t-
era tor with the same template parameters as the input stream iterator but no
Using Iterators 45
parameters for the constructor. This produces a special iterator object that is
recognized,as one position past the end of the stream.
Using stream iterators in a manual fashion like this does not take advantage
of their true power. Stream iterators were designed to be used in conjunction
with the STL functions that expect iterators as parameters. When used in this
manner, they can replace any iterator referencing a data structure stored in com-
puter memory. Let's examine how we could use them in some common STL
functions.
The STL function accumulate ( ) accepts two iterators delimiting a range
of elements and an initial value. It sums the elements in the range, adds the ini-
tial value, and returns the result. Getting the function to sum a series of values
read from cin is easily accomplished, as shown in Listing 2.10, where the input
is in the white box and the output is in the shaded box:
#include <iterator>
#include <iostream>
#include <numeric>
void maine)
{
istream_iterator<int, char> int_in(cin);
int sum;
sum = accumulate(int_in,
istream_iterator<int, char>(), 0);
cout « endl « "sum=" « sum « endl;
[1 2 3
I surn=6
summed. The final iterator is the usual past-the-end iterator constructed by in-
voking the parameterless constructor. The last parameter is the initial value that
will be added to the sum of the elements in the sequence.
One of the common uses of an ostream_i tera tor is to simplify the
printing of a sequence. Listing 2.11 shows how the copy () function can be
used to print out the contents of an array:
ostream_iterator<char*> str_out(cout,"\n");
char* text[3] = {"hello", "and" , "goodbye"};
hellO
[ and
g"QOdbye
1
Listing 2.11 - Using the copy ( ) Function with a Stream Iterator
The copy () function copies all of the values delimited by the first two it-
erators to the location referenced by the third iterator. This prints the three
words (hello, and, goodbye) on three separate lines, since the iterator con-
structor tells it to print a newline character between each value. Any type of for-
ward iterator could be used in place of the string pointer so that the same
technique can be used with any type of data structure.
Forward iterators are used to traverse a sequence in the forward direction one
element at a time. They differ from the stream iterators in that the dereference
operator can be used both to set and retrieve values. As with the stream itera-
tors, they are best suited to algorithms that make a single pass through the data.
The example in Listing 2.12 shows how to use a forward iterator to increment all
the values in a sequence by ten.
The use of the forward iterator is in the function AddlO ( ), which compares
the forward iterators for inequality and dereferences one of the iterators to both
retrieve and set the value. The function main () calls AddlO ( ), using integer
Using Iterators 47
pointers as forward iterators. Finally, the copy () function is passed two for-
ward iterators, which delimit the range to be printed.
#include <algorithm>
#include <iostream>
#include <iterator>
while(first 1= last) {
*first = ~first + 10;
first++;
main ( )
{
int i , da ta [ 10] ;
ostream_iterator<int> outstream(cout, " H);
for(i=0;i<10;i++) data[i] = i;
Add10(data, data+10);
copy(data, data+10, outstream);
cout « "\n";
return(O);
110 11 12 13 14 15 16 17 18 19
Finding the beginning and end of an array is relatively easy. The job is not
so easy for the STL containers, so the methods beg in () and end () are pro-
vided to locate the first member of the container and the past-the-end position.
These methods return iterators that can be used to traverse all the objects stored
in the container. Listing 2.13 demonstrates the use of these methods on a list.
The lis t is an STL container that implements a doubly linked list.
48 2: Iterators
#include <list>
#include <iterator>
#include <algorithm>
#include <iostream>
main ( )
{
list<int> listl;
list<int>: :iterator list_iter;
int i;
ostream_iterator<int> out_stream(cout, " H);
for(i=O;i<lO;i++) {
listl.push_back(i);
copy(listl.begin(),listl.end(),out_stream);
cout « endl;
list_iter = listl.begin();
while(list_iter != listl.end(»
{
cout « *list_iter++ « I ';
cout « endl;
return(O);
00 1 2 3 4 5 6 7 8 9
1. 1 2 3 4 5 6 7 8 9
The program begins by declaring a list of integers and an iterator for a list of
integers. Note that the template parameter for the list and its iterator must
match-you cannot use a float iterator to reference a list of integers. It also de-
clares an output stream iterator for the printing of integers. The for loop initial-
izes the list to contain the integers from 0 to 9, in order, by adding each of the
values to the tail of the list. The copy () function prints out the entire array
with the methods begin () and end ( ) used to delimit the values stored in the
Using Iterators 49
list. It would be impossible to find iterators referencing the beginning and end of
the list without the aid of these methods.
The rest of the program uses a while loop to retrieve the values from the
list one at a time and print them. An iterator is used for this purpose and is ini-
tialized to reference the first member of the list. Each time the loop executes, it
dereferences the iterator to retrieve the value it references, prints the value, and
then increments the iterator so that it will move to the next member of the list.
The loop terminates when the iterator becomes equal to the past-the-end iterator.
Some STL algorithms and data structures use iterators to indicate a specific
member of a container. One use of such an iterator is to allow insertion into a
list before a specific member of the list. The insert () method of the 1 ist
class performs this operation and requires an iterator indicating the element to
insert before and the value to insert, as shown in Listing 2.14:
list_iter = listl.begin();
list_iter++;
listl.insert(list_iter, 100);
copy(listl.begin(),listl.end(), out_stream);
cout « endl;
10 100 1 2 3 4 567 8 9
This code obtains an iterator referencing the start of the list and then incre-
ments it until it references the element before which the value should be inserted.
Since this is a forward iterator, it must be advanced one position at a time until
the desired element is reached.
2.4.3 Bidirectionallterators
A bidirectional iterator has all the properties of a forward iterator plus the ability
to move the iterator backward by decrementing it. This is very useful for algo-
rithms that require mUltiple passes through their data or that process the data in
reverse order.
There are actually two types of bidirectional iterators: a bidirectional itera-
tor and a reverse bidirectional iterator, the latter of which is created by applying
50 2: Iterators
#include <algorithm>
#include <list>
#include <iostream>
void maine)
{
list<int> listl;
list<int>::iterator list_iter;
int i;
list<int>: :reverse_iterator rev_iter;
ostream_iterator<int> out_stream (
cout, " ");
Using Iterators 51
for(i=O;i<lO;i++)
{
listl.push_back(i);
}
copy(listl.begin(),listl.end(),out_stream);
cout « endl;
101234 567 8 9
Everything works as expected, so we try to print the list out in reverse order,
as demonstrated in Listing 2.16:
list_iter = listl.end();
while(list_iter != listl.begin(»
{
i = *list_iter;
cout « i « ' ';
list_iter--;
cout « endl;
1-842150451 9 8 7 6 5 4 321
The first attempt to print in the reverse direction produces some strange re-
sults. The first number printed is random, and the printing halts one element too
soon. The problem is, as you might suspect, that we have used the methods pro-
vided for initializing forward iteration and attempted to use them for a reverse it-
eration. With a little reflection"we realize that the iterator was initialized to the
past-the-end iterator and when it was dereferenced, it produced an unpredictable
value. The while loop terminates when it reaches the first element of the list,
before it is printed, and hence stops printing before it reaches the beginning.
Obviously, we need to use r beg i n ( ) and rend ( ) , but to use these we need to
52 2: Iterators
use a reverse_iterator since that is the type they return. Let's try again
with the code in Listing 2.17:
rev_iter = listl.rbegin();
while(rev_iter 1= listl.rend(»
{
i = *rev_iter;
cout « i « ' ';
rev_iter++;
cout « endl;
1987 654 3 210
Now we get the correct result. One of the interesting points to note is that
when a reverse_i terator is incremented, it moves backward; and when it
is decremented, it moves forward. This is the opposite of the behavior demon-
strated by an i tera tor. This behavior means that any algorithm that accepts
two iterators delimiting its range of action simply has to increment the iterator to
move to the next element to be processed. If an i tera tor is passed to the al-
gorithm, it will move in the forward direction, whereas if passed a reverse-
_i tera tor, it will move backward. This greatly simplifies the writing of
algorithms that use iterators since the algorithm does not have to know which
type of iterator it is using-the iterator itself determines the direction of move-
ment through the sequence.
Random access iterators have all the capabilities of bidirectional iterators, plus
the ability to be moved in increments greater than one, the ability to be sub-
scripted, and the ability to be compared to determine if the position of one itera-
tor is before or after another.
The list used in the previous example supports bidirectional iterators but not
random access iterators. To see how random access iterators work, we have to
introduce a new container: the vector. The vector is similar to a regular array but
Using Iterators 53
has the added benefit that it can become larger should the storage requirements
be greater than originally anticipated. The main feature we are interested in at
the moment is that it supports random access iterators. A vector is constructed
and initialized in the same way as a list, as in Listing 2.18:
vector<int> vectl;
vector<int>: :iterator iterl;
vector<int>: :reverse_iterator rev_iter;
int i;
ostream_iterator<int> out_stream(cout," ");
for(i=O;i<lO;i++) vectl.push_back(i);
copy(vectl.begin(), vectl.end(), out_stream);
cout « endl;
0123456 7 8 9
iterl = vectl.begin();
iterl += 3;
vectl.insert(iterl, 99);
copy(vectl.begin(), vectl.end(), out_stream);
cout « endl;
10 1 2 99 4 5 6 789
iterl = vectl.begin();
vectl.insert(iterl+3, 88);
copy(vectl.begin(), vectl.end(), out_stream);
cout « endl;
[012 88 99 3 4 567 8 9
iterl = vectl.begin();
vectl.insert(iterl+3, 88);
iterl = vectl.begin();
for(i=O;i<S;i++) cout « iterl[i] « ' ';
cout « endl;
10 1 2 88 99
rev_iter = vectl.rbegin();
while(rev_iter 1= vectl.rend(»
{
i = *rev_iter;
cout « i « , '. I
Using Iterators 55
rev_iter++;
cout « endl;
1987 654 3 99 88 2 1 0
The final new operation provided by random access iterators is the ability to
compare iterators to find if one comes before the other. This allows us to rewrite
the loop to print out the first six values of the vector as shown in Listing 2.23:
iter2 = vectl.begin() + 5;
iterl = vectl.begin();
while(iterl <= iter2)
{
i = *iterl;
cout « i « I ';
iterl++;
}
cout « endl;
1012 88 99 3
list<int> listl;
int i, dist;
for(i=O;i<lO;i++) listl.push_back(i);
dist = distance(listl.begin(), listl.end(»;
cout « "dist=" « dist « endl;
!dist=10
The STL defines a series of iterator tags that correspond to the iterator cate-
gories. The tags are defined as follows:
namespace std {
struct input_iterator_tag {}i
struct output_iterator_tag {}i
struct forward_iterator_tag:
public input_iterator_tag {};
struct bidirectional_iterator_tag:
public forward_iterator_tag {};
struct random_access_iterator_tag:
public bidirectional_iterator_tag {};
}
These tags are simply empty structures. It is not the content of the tag that is
important, but the type of the tag. Every iterator must define a member i tera -
tor_category that returns one of the five iterator tags. This can be used to
provide specialized implementations of functions that take advantage of the ca-
pabilities of different iterator types.
The HP version of the STL makes use of a function iterator_-
ca tegory () that, when passed an iterator, returns its iterator tag. Similarly,
the functions value_type ( ) and distance_type ( ) return the type refer-
enced by an iterator and a type to represent the difference between two iterators.
These functions have been removed from the C++ standard, but you might find
that some implementations still support them. If possible, you should use the re-
placement, i tera tor_ tra its, rather than these older functions.
This takes the iterator as a template parameter and simply defines its own
types in terms of the types defined by the iterator. The specialization for point-
ers, shown below, provides definitions that are appropriate for pointers.
template<class T>
struct iterator_traits<T*> {
typedef ptrdiff_t difference_type;
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef random_access_iterator_tag
Iterator Traits 59
iterator_category;
};
#include <iostream>
#include <iterator>
#include <vector>
#include <list>
#include <utility>
ptrdiff_t dist;
Randomlterator probe;
Randomlterator top;
Randomlterator bot;
60 2: Iterators
bot = first;
top = last - 1;
while (top > bot)
{
dist = top - bot;
probe = bot + dist;
if((*probe) == val)
return probe;
else
if((*probe) < val)
bot probe + 1;
else
top probe - 1;
}
return last;
return last;
}
void main()
{
vector<int> vect;
list<int> 1st;
int i;
« endl;
The program in Listing 2.25 shows how a function can be specialized to take
advantage of the capabilities of the iterator it is passed. The example function
searches a sequence to find the location of the first occurrence of a value. The
function to perform the search is called bsearch () and does nothing but call
the function _bsearch ( ). There are two versions of _bsearch ( ): the first
uses a random access iterator to perform a binary search, and the second uses a
forward iterator to perform a linear search. The version that is invoked is deter-
mined by the call from bsearch ( ):
return _bsearch(first, last, val, _Iter_cat(first»;
This includes one extra parameter that indicates the category of the iterator
passed to the function. _Iter_cat ( ) is a function provided in Microsoft Vis-
ual C++ 5.0 for determining the category of an iterator. It returns the iterator tag
for the iterator.
Examining the signatures for _bsearch ( ), we see that the first requires a
randoID_access_i terator_tag as its fourth parameter, while the second
requires a forward_iterator_tag. Thus, if a random access iterator is
passed to bsearch ( ), the binary search is used, and if a forward iterator is
passed, a linear search is performed. This allows the best algorithm to be se-
lected based on the capabilities of the iterator.
_I ter _ ca t ( ) is specific to the Microsoft implementation. It performs the
same function as the older function i tera tor_ca tegory ( ), which is being
phased out and was removed from the implementation. This is an interim solu-
tion to the problem that i tera tor_trai ts is not fully supported. Once
i terator_trai ts is fully implemented, the expression
62 2: Iterators
_Iter_cat(first)
can be replaced by
type name
iterator_traits<Randomlterator>::iterator_category
Similarly, the declaration
ptrdiff_t dist;
can be replaced by
typename
iterator_traits<Randomlterator>::difference_type dist;
which will allow the type of dist to be set appropriately to the iterator used.
Eventually, all compilers will conform to the standard and will use i tera-
tor_trai ts. In the interim, you will have to check to determine the exact fa-
cilities provided by your compiler.
3
The STL Algorithms
3.1 Introduction
The Standard Template Library provides a large number of generic algorithms
that can greatly simplify routine programming tasks. Although the number of al-
gorithms might seem overwhelming at first, they can be broken into categories
that make them easier to comprehend.
Several important concepts need to be introduced before you begin to ex-
plore the algorithms themselves. These include operator templates, function ob-
jects, naming conventions, and the layout of the algorithms. Each of these is
explained in the following sections.
The STL defines a number of comparison operator templates that reduce the
number of operators that must be implemented for the container classes in the li-
brary. The container classes implement a minimal number of comparison opera-
tors, and all other comparison operators are generated from templates. The
operator templates use only the minimal operators to implement their operation.
Many functions accept a pointer to another function that they call during exe-
cution. The function pointer passed provides a method to customize the algo-
rithm implemented by the function to which it is passed. The STL generalizes
this concept to the function object, which can be used interchangeably with a
pointer to a function. A function object is any object that supports opera-
tor ( ) , the function call operator.
The STL provides function objects for all the common operators so that they
can be passed as parameters where a function object is required. The STL also
defines function adaptors that can combine functions and their parameters to
yield a function object.
3.2 Operators
When we examine the STL containers in the HP implementation, we find that
they implement opera tor== and opera tor< and no others. The remaining
comparison operators are templates that implement their computations in terms
of operator== and operator<. The C++ standard changed this so that
each class implements the full set of operators. The relational templates were
moved to the namespace rel_ops within the namespace std.
The following shows how we could write a template version of opera-
tor! = that is implemented in terms of operator==:
template <class T>
bool operator!=(const T& a, const T& b)
{
return ( ! (a == b»;
The other comparison operators are implemented similarly, yielding the total
set:
• operator! =
• opera tor<=
• operator>
• opera tor>=
One other benefit of having these templates defined for you is that, after you
include the appropriate STL header file <uti 1 it y>, you can use the templates
for any classes you define yourself. All you have to do is define opera tor==
and opera tor< for your classes, and the other comparisons will be constructed
from the templates when required. These operators should be implemented as
nonmember functions. This is demonstrated in Listing 3.1.
Operators 65
#include <utility>
#include <string>
#include <iostream>
class Employee
{
private:
string namei
int idi
public:
Employee(string nm, int eid):
name(nm), id(eid) {}
friend bool operator==(const Employee& e1,
const Employee& e2)i
friend bool operator«const Employee& e1,
const Employee& e2)i
friend ostream& operator«(ostream& os,
Employee& e)i
}i
void main ()
{
Employee el("Smith", 123)i
Employee e2("Jones", 456)i
i f (e1 ! = e2)
66 3: The STL Algorithms
Since the relational operators are members of the namespace rel_ops, this
is merged with the global namespace with the using statement. It is also possi-
ble to qualify the relational operator if you do not want to merge all of the rela-
tional operators into the global namespace. The qualification looks like this:
if(rel_ops: :operator!=(el, e2» { ... }
The other operators are implemented as function objects in the same way.
The following shows the complete list of function objects defined by the STL
with the operator they implement shown in parentheses after the name. Users of
the HP implementation of the STL should note that mul tiplies ( ) was called
times ( ) in that version.
These function objects can be passed to any of the STL functions that require
a binary function. One such function 'is transform ( ), which combines the
elements in two sequences on an element-by-element basis to yield a third se-
quence. The function requires a binary function object that is used to combine
two elements to yield a new element. This is demonstrated in Listing 3.2.
68 3: The STL Algorithms
#include <iterator>
#include <algorithm>
#include <functional>
#include <iostream>
void main()
{
int inl[10), in2[10),out[10),i;
int* inl_end;
ostream_iterator<int> out_stream(cout, II ");
for(i=O;i<10;i++) {
inl[i) i;
in2[i) = i * 3;
The transform() function invokes the call operator on the function ob-
ject passed to it, providing it with the necessary parameters drawn from the two
sequences. This is typical of how function objects can be passed to a function.
Many of the STL functions expect a function object that will act as a predi-
cate. A predicate is a function that accepts one or two parameters and returns a
Boolean value indicating whether the value(s) satisfy the predicate. A typical
function object that could be used as a predicate is less. This function object
must be supplied with two parameters representing the values to be compared.
Passing this function object to another function that works with a single se-
quence is insufficient since the receiving function will apply it to a member of a
sequence and not have a second parameter for the predicate.
Consider the STL function replace_if ( ), which will traverse the mem-
bers of a sequence replacing all those that satisfy a predicate with a new value.
The prototype for replace_if ( ) looks like this:
Function Objects 69
usually references the element immediately after the last element in the
sequence.
• Many algorithms have both an in-place version and a copying version.
The in-place version performs its operation on a sequence in its original
location. The copying version copies the input sequence to a new
location, performing its operation during the copy, leaving the original
sequence unchanged. The copying version of the function has the same
name as the in-place version with the suffix _copy appended. If an
operation must be performed and the result copied to another location,
then it is more efficient to use the copying version of the algorithm than
to use the in-place version followed by a copy. For some algorithms,
such as sorts, this is not true, and copying versions of the algorithms are
not provided.
• Many algorithms have an unconditional version and a predicate version.
The unconditional version performs its operation on every member of a
sequence, whereas the predicate version operates on only those members
of the sequence for which the predicate is satisfied. The predicate
versions of the functions have the suffix if.
• You should note the type of iterator expected in the function prototype.
This indicates that the function requires an iterator with at least these
capabilities. You might find that some algorithms require at least a
forward iterator and cannot use an input iterator, whereas others require
the full capabilities of a random access iterator. If you pass an iterator
with less than the required capabilities, the code will fail to compile when
the function attempts to use an operator not provided by the iterator.
To understand the large number of algorithms in the STL, it helps to break them
into a series of categories, as depicted in Figure 3.1.
Sequence Algorithms Provide a variety of operations for manipulating se-
quences of elements. These include function applica-
tion, rearranging elements, assigning values to
multiple elements, copying sequences, searching, and
element deletion. The sequence algorithms are di-
vided into mutating sequence algorithms and nonmu-
tating sequence algorithms. Mutating sequence
algorithms modify the values in the sequence to
which they are applied. Nonmutating sequence
The STL Algorithms 71
material in each chapter and then proceed to the algorithms of interest. Although
an understanding of the algorithms is not essential to using the containers, it is
recommended for the general reader. If your main interest is containers, you
should skim the algorithms to familiarize yourself with the operations that can be
performed on containers.
4
Sequence Algorithms
4.1 Introduction
This chapter examines the sequence algorithms-those that treat their data as or-
dered or unordered sequences. Although there are a great number of sequence
algorithms, they are divided into the subcategories of mutating sequence algo-
rithms and nonmutating sequence algorithms. Nonmutating sequence algo-
rithms, also called nonmodifying sequence algorithms, treat the sequence to
which they are applied as read-only and leave the sequence unchanged after they
terminate. Mutating sequence algorithms change the order of the sequence or
the values that constitute the sequence.
4.2 Preliminaries
The Standard Template Library contains many different functions and data struc-
tures to understand. Deciding where to start the study of this mass of material is
a difficult decision since you cannot understand many of the parts until you un-
derstand the parts on which they depend. You want to start with the basics, yet
in order to demonstrate the basics you must resort to using more advanced fea-
tures. One way to tackle this would be to start reading and, when an unfamiliar
concept is encountered, skip to the appropriate section and read that before con-
tinuing. There are two reasons this approach is not workable. First, books are
necessarily sequential and, no matter how well cross-indexed, a lot of page turn-
ing and book marking remains to be done. Second, the human mind does not
like its train of thought interrupted. Readers struggling to understand a concept
resent having to tum to a separate section, read another topic, and then return to
the first, trying to remember where they were.
#include <algorithm>
#include <list>
#include <iostream>
maine )
{
int ar[lO], i;
list<int> listl;
ostream_iterator<int> outstream(cout, II ");
for(i=O;i<lO;i++) {
ar [i] = i;
listl.push_back(O);
returneD);
012 3 4 567 8 9
000 0 0 0 0 0 0 0
012 3 4 567 8 9
It is assumed that you know what an array is; however, you might not under-
stand the list as well. The term list refers to the linked list, a data structure com-
monly used in computer programs. A linked list is an example of a container
data structure, since it stores other data structures. A list consists of a series of
nodes joined together by pointers so that they form a chain. The list imple-
mented in the STL is actually a doubly linked list, meaning that each node has a
pointer to the node behind it as well as to the node in front of it. The nodes in a
list are implemented as structures or classes, with a typical declaration looking
like the following:
template <class T>
struct ListNode {
T data;
ListNode* next;
ListNode* prey;
}i
Each list node contains an instance of the type of object being stored in the
list. It also has pointers to the next node in the list and the previous node. The
node at the beginning of the list has a previous pointer equal to NULL, and the
node at the end of the list has a next pointer equal to NULL. These are unique
pointer values that are used to mark each end of the list.
A list normally has a head pointer that references the first node in the list. A
typical list is depicted in Figure 4.1.
76 4: Sequence Algorithms
~ = NULL pointer
(head~ data data data
next next ,. next h
r prev ~
prev ~ prev
All of the functions and containers in the STL are defined as templates so
that they can work with any type of data. The 1 is t template requires a parame-
ter indicating the type of value to be stored in the list-in this case, a series of in-
teger values. The method push_back () appends a new value as the last
member of the list.
An ostream_i tera tor is a special type of iterator that is associated with
an output stream. It functions just like any other iterator except that when it is
dereferenced and a value assigned to it, it prints the value on the associated
stream. The constructor for an ostream_i tera tor requires the name of the
stream and a character string that will be printed after every value. An 0-
stream_i tera tor can be used whenever an algorithm expects an iterator to
which it will copy values. The effect will be to copy the values to an output
stream rather than an in-memory location.
The copy () function takes three iterators as parameters. The first two de-
limit the range of values to be copied, and the third indicates the position to
which the values will be copied. After the function completes, a copy of the val-
ues from the first iterator up to the second has been placed at the location start-
ing at the third iterator.
Finding the beginning and end of an array is simple: the first member is lo-
cated at the start of the array, and the last member is the address of the first ele-
ment plus the number of elements. Finding the endpoints of a list is more
difficult since the individual nodes are not stored contiguously. The list class
provides two methods for this purpose: begin ( ), which returns an iterator ref-
erencing the start of the list and end ( ) , which returns an iterator referencing the
element one past the end of the list.
One of the advantages of iterators is that they work the same regardless of the
data structure to which they are applied. This allows the copy ( ) function to be
Preliminaries 77
used to copy values from the array to an output stream as well as copying from
the array to the list.
All of these STL objects and concepts are used in the examples that follow.
Where others are used, they are explained as necessary. A more detailed discus-
sion of these topics can be found by skipping ahead to the appropriate sections.
4.2.1 Pairs
Some STL algorithms return a pair of values rather than a single value. This is
common in algorithms dealing with associative containers. Returning a pair of
values is sufficiently common that a data structure has been created for the pur-
pose. It is called a pair and is defined as follows:
template <class TI, class T2>
struct pair {
typedef TI first_type;
typedef T2 second_type;
TI first;
T2 second;
};
The pair is returned as a single value but allows access to the two values
contained within it using the usual dot notation to access a field in a structure.
The pair has three constructors:
pair();
#include <utility>
#include <iostream>
void main ( )
{
pair<int, double> pidl(2, 3.14);
pair<int, double> pid2(1, 2.4);
pair<int, double> pid3;
The program in Listing 4.2 shows how to build a pair by specifying the
template parameters as well as building a pa ir using make_pa ir () where
the types of the parameters are deduced from the types of the arguments. The
definition of operator< is interesting. For two pairs, pl and p2, pl is less
than p2 if pl.first is less than p2.first, regardless of the value of the second
member. If the first member of each pair is equal, then pl is less than p2
only if the pl.second is less than p2.second.
4.3.1 Counting
We start our study of the sequence algorithms with a relatively simple algorithm
for counting. The counting algorithm searches a sequence for values that satisfy
some criterion and returns a parameter indicating the number of times a value
satisfying the criterion is found in the sequence. The algorithm makes no as-
sumptions about the order of the values in the sequence and traverses the entire
sequence to perform the count. Since the algorithm makes only a single pass
through the data, the complexity is O(n).
As with many of the algorithms we will examine, there are two functions that
implement the count algorithm: count () and count_if ( ). The count ( )
function searches for a specific value and returns the number of times it occurs in
the sequence, and the count_if () function counts the number of values that
satisfy a predicate. Prototypes for the two functions follow:
template <class Inputlterator, class T>
typename
iterator_traits<Inputlterator>: : difference_type
count(Inputlterator first,
Inputlterator last,
const T& value);
#include <algorithm>
#include <iostream>
main ( )
{
int ar[lO),i,cti
ostream_iterator<int> outStream(cout, " ")i
for(i=Oii<lOii++) ar[i) = ii
ar[2) = 5i
copy(ar, ar+lO, outStream)i
ct = count(ar,ar+l0,5)i
cout « "\nThere are" « ct « " fives\n"i
return(O)i
[ 0 15
3 4 567 8 9
There are 2 fives
#include <algorithm>
#include <iostream>
#include <functional>
#include <iterator>
main ( )
{
int ar[lO],i,ct;
ostream_iterator<int> outstream(cout, " H);
for(i=O;i<lO;i++) ar[i] = i;
ar[2] = 5;
copy(ar, ar+l0, outstream);
ct = 0;
ct = count_if(ar, ar+l0, bind2nd(less<int>(), 5»;
cout « "\nThere are " « ct «
" less than five\n";
return(O);
~ There
o1 5 3456.789
are 4 leS8 than five
4.3.2 Finding
The finding algorithm searches a sequence for the first occurrence of a value that
satisfies some criterion. The algorithm terminates either when it finds the value
for which it is searching or when it encounters the end of the sequence. The
complexity is O(n).
There are two functions that implement the finding algorithm: find ( ) and
find_if ( ):
template <class Inputlterator, class T>
Inputlterator find(Inputlterator first,
Inputlterator last,
const T& value);
template <class Inputlterator, class Predicate>
Inputlterator find_if(Inputlterator first,
Inputlterator last,
Predicate pred);
The function find() searches the sequence from first up to last for
value. It returns an iterator referencing the first occurrence of value. If
value does not occur in the sequence, then an iterator equal to last is
returned.
The function find_if ( ) searches the sequence for the first value that satis-
fies the predicate. If such a value exists, an iterator referencing it is returned;
otherwise, an iterator equal to last is returned.
class Forwardlterator2,
class BinaryPredicate>
Forwardlteratorl
find_first_of(Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2,
BinaryPredicate pred) ;
#include <algorithm>
#include <iostream>
#include <string.h>
void maine)
{
char str[16];
char vowels[16];
char *locn, *endstr, *endVowels;
cout « locn[O);
locn = find_first_of(locn + I, endStr, vowels,
endVowels) ;
cout « endl;
I Eiie
The algorithm for finding adjacent values searches a sequence for two adjacent
values that satisfy some relationship between them. Although it is commonly
used to find two adjacent values that are equal to each other, other relationships
can be specified. The algorithm returns an iterator referencing the first member
of the first pair of values satisfying the relationship. If no such pair of values ex-
ists' the past-the-end iterator for the sequence is returned. The complexity of
the algorithm is O(n).
The algorithm is implemented as a single function, adj acent_f ind ( ),
that is overloaded with two signatures:
template <class Forwardlterator>
Forwardlterator adjacent_find(Forwardlterator first,
Forwardlterator last) ;
#include <algorithm>
#include <iostream>
#include <iterator>
#include <functional>
main ( )
{
int ar[lO], i, *iter;
ostream iterator<int> outStream(cout, " H);
greater<int> greater_than;
for(i=O;i<lO;i++) ar[i] = i;
ar [5] = 9;
copy(ar, ar+lO, outStream);
cout« '\n';
iter = adjacent_find(ar, ar+lO, greater_than);
if(iter == ar + 10) cout « "none found\n";
else cout « "first is greater" « *iter « " "
« *(iter+l) « '\n';
return 0;
[ 0 1 2 3 4 967 8 9
first is greater 9 6
4.3.5 ForEach
The ForEach algorithm traverses a sequence and calls a function for every mem-
ber of the sequence. The function is passed a single parameter, which is the
Nonmutating Sequence Algorithms 87
value obtained by dereferencing the iterator used to traverse the sequence. Any
value returned by the function that is called is discarded. The algorithm makes a
single pass through the entire sequence and, hence, has a complexity of O(n).
The forEach algorithm is implemented by the function for_each ( ):
template <class Inputlterator, class Function>
Function for_each(Inputlterator first,
Inputlterator last,
Function f);
Listing 4.7 uses the function object mul tiplies to attempt to double the
values in the sequence. This will work only if for_each ( ) assigns the result
of the function call to a member of the sequence, as shown in the following:
*first = f(*first, 2).
for_each () does not make such an assignment, but simply invokes the
function and discards the result, as demonstrated:
#include <algorithm>
#include <iostream>
#include <functional>
main ( )
{
int ar[lO],i;
ostream_iterator<int> outstream(cout, " H);
for(i=O;i<lO;i++) ar[i] = i;
for_each(ar, ar+10, bind2nd(multiplies<int>(),2»;
copy(ar, ar+10, outStream);
cout « '\n';
return 0;
[0 1 2 3 4 5 6 7 8 9
Obviously, this is not the right way to use for_each ( ). A function that
does nothing other than compute a value based on its parameters and return this
value as a result is called a pure function. A function is said to have a side ef-
fect when it modifies its environment by assigning to a variable in the surround-
ing environment or making some other change outside itself. Since
for_each ( ) discards the result of the function it calls, we must use a function
that has a side effect to see something actually happen. For this, we have to con-
struct our own function object, as in Listing 4.8.
#include <algorithm>
#include <iostream>
#include <functional>
main ( )
(
int ar[lO),i;
for(i=O;i<lO;i++) ar[i) = i;
return(O);
}
16 7
r---------~--~--~---c----~--~------------~
B 9
This works since the function object passed to for_each () makes a per-
manent change to its environment: it prints something. Such a change is not lost
when the result of the function is discarded, so the change remains after the func-
tion terminates.
It is interesting to see how the function object is created. In the definition of
a function object, you saw that it was any object for which opera tor () was
defined. Thus, to create a function object, all you need do is create an object
that defines opera tor ( ). The body of opera tor () is called whenever the
function object is invoked, so that is where the code to be executed is placed.
PrintGt5 is a class template, so it can be used with many different types.
The applicability of the function is restricted by the fact that opera tor ( )
compares each value to T ( 5 ) , implying that there must be a conversion operator
from int to type T. For special cases, it is not necessary to create a class tem-
plate, although this will restrict the applicability of the class. This is demon-
strated in Listing 4.9 which is only able to double the value of integers.
You are probably wondering if it is possible to write a function object that
will modify the values in the sequence to which it is applied. The answer is
yes-all that has to be done is to pass a reference to the value in the sequence
rather than the value itself.
#include <algorithm>
#include <iostream>
#include <functional>
class Double:
public unary_function<int, void>
public:
Double(){}
void operator() (int& a) {a *= 2i}
}i
main ( )
{
int ar[lO),ii
ostream_iterator<int> outstream(cout, " ")i
for(i=Oii<lOii++) ar[i) = ii
returneD);
1024 6 8 10 12 14 16 18
Here we see that the function modifies its environment by changing the val-
ues passed to it. Since they are passed by reference, the change affects values
outside the function, and the changes remain after the function terminates.
You might be wondering whether this is a misuse of the for_each ( ) func-
tion. It is supposed to be a nonmutating function, yet we were able to get it to
modify the sequence to which it was applied. This is not the intended use of the
function, but the STL has no way to prevent you from writing a function to mod-
ify the values to which it is applied. It is preferable to use the function trans-
form ( ), described in the section on mutating sequence algorithms, which is
provided to allow sequences to be modified.
pair<Inputlteratorl, Inputlterator2>
mismatch(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Binarypredicate binary_pred);
The first form of the function returns an iterator pair that references the first
two mismatching elements in the two sequences. This form of the function uses
opera tor== to perform the element-by-element comparison. The second
form uses a user-supplied binary predicate to perform the comparison.
Listing 4.10 demonstrates how the second form of the function can be used to
find the first pair of elements in two sequences where the element in the first se-
quence is not less than or equal to the corresponding element in the second
sequence.
#include <algorithm>
#include <iostream>
#include <functional>
main ()
{
int arl[lO], ar2[lO], i;
ostream_iterator<int> outstream(cout, " H);
pair<int*, int*> position(O,O);
for(i=O;i<lO;i++) arl[i] = ar2[i] = i;
ar2 [5] = 0;
copy(arl, arl+10, outstream);
cout« '\n';
copy(ar2, ar2+10, outstream);
cout « '\n';
position = mismatch(arl, arl+10, ar2,
less_equal<int>(»;
if(position.first == arl+10)
cout « "None found";
else
cout « *(position.first) « ", " «
*(position.second) « '\n';
92 4: Sequence Algorithms
returneD);
0 1 2 3 4 5 6 7 B 9 1
[o 1 2 3 4 0 6 7 8 9
5, 0
'------~-----'
One of the new concepts in this program is the idea of returning two iterators
as a single object-the pair. The pair is a structure template that contains
two members, first and second. The pair template is parameterized with
the type of each member so that it can be used for pairs of any type. In this case,
an instance of two iterators of the correct type is created. Although the pair
does provide a parameterless constructor, position must be declared with the
correct template parameters and initialized with two values of the correct type.
The call to misma tch () is fairly straightforward, and the binary predicate
is generated in the usual manner. The result is assigned to the pair declared
previously, which is then examined to determine if and where a mismatch was
found.
Whereas the mismatch algorithm compared sequences to find the first non-
matching members, the equality algorithm returns a Boolean result indicating
whether two entire sequences are the same. In the general case, it determines if
all element-by-element comparisons of two sequences satisfy a predicate. Since
the algorithm involves a linear pass through two sequences, the complexity is
O(n). The implementation is the function equal ( ), which is overloaded:
template <class Inputlteratorl, class Inputlterator2>
bool equal(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2);
Inputlterator2 first2,
BinaryPredicate binary _pred) ;
Both forms of the function compare the elements in the sequence from
first1 up to last1 with those in the sequence beginning at first2 of the
same length. The first form uses operator== to perform the comparisons,
and the second allows you to specify a binary predicate. This can be used to de-
termine if all values in the first sequence are less than those in the second, and
similar operations. Listing 4.11 uses equal ( ) to determine if all values in one
sequence are less .than all values in another sequence.
#include <algorithm>
#include <iostream>
#include <functional>
maine )
{
int ar1[10], ar2[10], i, result;
ostream_iterator<int> outstream(cout," ");
for(i=O;i<lO;i++) {
ar2 [i] 10 + i;
arl [i] = i;
return(O);
012 3 456 7 8 9
10 11 12 13 14 15 16 17 18 19
They are all less
4.3.8 Searching
The find algorithm locates the first occurrence of a single value in a sequence.
The search algorithm, on the other hand, locates the first occurrence of a smaller
sequence within a larger sequence. This is equivalent to searching for a sub-
string within a larger string. The algorithm returns an iterator that references the
first occurrence of the subsequence within the larger sequence or an iterator
equal to the past-the-end iterator for the sequence being searched if a matching
subsequence is not found. A variation on the search algorithm searches for the
last occurrence of a subsequence rather than the first. If a predicate other than
equality is used, the algorithm will search for the subsequence whose members
satisfy the predicate specified. Due to the increased number of comparisons nec-
essary to compare sequences, the complexity of the algorithm is quadratic.
The algorithm is implemented by the overloaded functions search ( ),
search_n ( ), and find_end ( ), each of which has two signatures. The
function search () locates the first occurrence of the subsequence, and
search_n () locates a subsequence of count repeated elements. find-
_end ( ) locates the last occurrence of a subsequence.
template <class Forwardlteratorl,
class Forwardlterator2>
Forwardlteratorl search(
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2);
#include <algorithm>
#include <string.h>
#include <iostream>
using namespace std;
maine )
{
char str[64], subStr[16] , *iter, *iterl;
ostream_iterator<char> outStream(cout, "");
subStr + strlen(subStr»;
if(iter == (str + strlen(str») cout «
"not found\n";
else {
iterl = find(iter, iter + strlen(iter), I ');
return(O);
(Mary
As you can see, the STL algorithms can be combined to work with the exist-
ing string functions without problems. The function strlen ( ) provides a con-
venient method to generate past-the-end iterators. Once the beginning of the
string is located, the find () function is used to locate the space at the end of
the substring, and the copy ( ) function, in conjunction with an os tream_i t-
era tor, is used to print out the result.
Listing 4.13 demonstrates the use of find_end ().
#include <algorithm>
#include <iostream>
#include <string.h>
void maine)
{
char str[64];
char substr[B];
char* locn;
strcpy(str,
"Halloween night is the night of fright");
strcpy(substr, "night");
else
{
cout « locn « endl;
lnight of fright
4.4.1 Copy
The copy algorithm has been used frequently in the examples in previous sec-
tions. This demonstrates that often the simplest, most mundane operations are
used more frequently than far more complex ones. The function of the copy al-
gorithm is to copy the elements from one sequence to another. The copy algo-
rithm is performed in linear time, O(nJ. There are two implementations of the
copy algorithm: copy ( ) and copy_backward ( ):
template <class Inputlterator, class Outputlterator>
Outputlterator copy(Inputlterator first,
98 4: Sequence Algorithms
Inputlterator last,
Outputlterator result) ;
#include <algorithm>
#include <iostream>
main( )
{
int i, ar[lO];
II initialize sequence
fill(ar,ar+10,O);
Mutating Sequence Algorithms 99
initial sequence
123 4 5 000 0 0
after copy(ar, ar+5, ar+3)
12312 312 0 0
after copy-backward(ar, ar+5, ar+8)
123123450 0
4.4.2 Swapping
The swapping algorithm exchanges two values or the contents of two sequences.
Swapping of two values takes constant time, and the swapping of ranges takes
linear time. Three functions are used to implement the swapping algorithm:
swap ( ), iter_swap ( ), and swap_ranges ( ):
template <class T>
void swap(T& a, T& b);
100 4: Sequence Algorithms
#include <algorithm>
#include <iostream>
maine )
{
int arl [10], ar2 [10], i;
ostream_iterator<int> outstream(cout, " H);
returneD);
4.4.3 Filling
The filling algorithm is used to fill all or part of a sequence with copies of the
same value. This operation is performed in linear time. The algorithm is useful
for the initialization of sequences. Two functions are used to implement the fill
algorithm-f i 11 ( ) and f i 11_n ( ) :
template <class Forwardlterator, class T>
void fill(Forwardlterator first,
Forwardlterator last,
const T& value);
#include <algorithm>
#include <iostream>
main ( )
{
int ar[lO], i;
ostream_iterator<int> outStream(cout, " H);
return(O);
10 49 49 49 49 49 6 7 8 9
4.4.4 Generate
Like the fill algorithm, the generate algorithm fills all or part of a sequence with
values. Whereas the fill algorithm used the same value all the time, the generate
algorithm uses potentially different values returned from a function object that
generates the values. Assuming the function object that generates the values
takes constant time, the generate algorithm will take linear time. The generate
algorithm is implemented by the functions genera te () and generate-
_n ():
template <class Forwardlterator, class Generator>
void generate(Forwardlterator first,
Forwardlterator last,
Generator gen);
Generator gen) ;
The function generate ( ) fills the sequence from first up to last with
successive values returned from the generator, which must be a parameterless
function object. The function genera te_n ( ) fills the next n values of the se-
quence beginning with first with the values returned by successive calls to the
generator.
Once again, we are faced with the problem of providing a function object for
use by a function. The function object must provide opera tor ( ) , and it is ex-
pected to return a possibly unique value each time the generate function calls
gen. opera tor ( ). The calculation performed by the function object and the
values returned are left to the needs and discretion of the programmer. One of
the simplest function objects that could be used is one that simply returns a con-
stant value:
class MakePi {
public:
Makepi() {}
float operator() () {return 3.14159;}
};
This satisfies the requirements of a function object but returns the same value
each time it is called. The same effect could be achieved using the function
fill ( ). A more useful example is shown in Listing 4.17, where the function
object always returns twice the value it returned the last time it was called.
#include <algorithm>
#include <iostream>
};
104 4: Sequence Algorithms
main ()
{
int ar[lO] ;
ostream_iterator<int> outstream(cout, " H);
int initValue = 2;
return(O);
Here the function object Gener maintains a data member in which it stores
the next value to be returned. Each time opera tor ( ) is invoked, it calculates
the next value and returns the current one. The Gener class has a constructor
that initializes the value to be returned. When initialized with a value of 2, it
proceeds to generate the successive powers of 2.
The version of the STL distributed by the Rensselaer Polytechnic Institute in-
cludes a function object, IotaGen, that works just like this. It is constructed
with an initial value, and each time opera tor ( ) is called, it returns the current
value and then increments it using operator++. The result is a series of con-
secutive values similar to the ones returned by the APL function 1 (iota). The
SGI distribution includes a nonstandard function, iota ( ), which fills a se-
quence with successive values of a type that supports opera tor++.
4.4.5 Replace
The replace algorithm replaces all occurrences of values in a sequence that sat-
isfy a predicate with a new value. The entire sequence is traversed, yielding a
linear time operation. The functions replace ( ) and replace_if ( ) imple-
ment the replace algorithm:
template <class Forwardlterator, class T>
void replace(Forwardlterator first,
Forwardlterator last,
Mutating Sequence Algorithms 105
#include <algorithm>
#include <iostream>
#include <functional>
main ( )
{
int ar[lO], i;
106 4: Sequence Algorithms
return(O);
10 1 2 3 4 5 -1 -1 -1 -1
4.4.6 Transfonn
The transform algorithm takes one or two sequences as input and produces a new
sequence by applying a function to them. If only one sequence is used, a unary
function must be supplied to transform each value into that which will be placed
in the result sequence. If two sequences are used, a binary function must be sup-
plied that will be applied to corresponding members of the two input sequences
to yield a new value for the output sequence. The algorithm takes linear time,
assuming the function it calls takes constant time. The algorithm is implemented
by the transform function, which has two signatures:
template <class Inputlterator, class Outputlterator,
class UnaryOperation>
Outputlterator transform(Inputlterator first,
Inputlterator last,
Outputlterator result,
unaryOperation op) ;
The first form works on a single sequence and requires a unary function ob-
ject. The resultant sequence is of the same length as the input sequence and is
placed at the location referenced by resul t. The second form works with two
input sequences and expects a binary function object. Both functions return a
past-the-end iterator for the result sequence. Either of the input sequences can
overlap the result sequence without destruction of the result sequence. If such an
overlap occurs, all or part of one of the input sequences will be replaced by all or
part of the result sequence, as shown in Listing 4.19.
#include <algorithm>
#include <iostream>
#include <functional>
maine )
{
int arl[lO], ar2[lO], result[lO] , ii
ostream_iterator<int> outstream(cout, II ")i
return(O)i
o 1 2 3 4 5 6 7 8 9
o 0 112 2 3 3 4 4
o 0 2 3 8 10 18 21 32 36
4.4.7 Remove
The remove algorithm removes all elements from a sequence that satisfy a condi-
tion. The result sequence will be shorter than the original sequence if any values
were removed. This is a linear time algorithm. Four functions are used to im-
plement the remove algorithm: remove (), remove_if (), re-
move_copy ( ) , and remove_copy_i f ( ):
template <class Forwardlterator, class T>
Forwardlterator remove(Forwardlterator first,
Forwardlterator last,
const T& value) ;
template <class Forwardlterator, class Predicate>
Forwardlterator remove_if(Forwardlterator first,
Forwardlterator last,
Predicate pred);
template <class Inputlterator, class outputlterator,
class T>
outputlterator remove_copy(Inputlterator first,
Inputlterator last,
outputlterator result,
const T& value) ;
template <class Inputlterator, class Outputlterator,
class Predicate>
Outputlterator remove_copy_if(Inputlterator first,
Inputlterator last,
Outputlterator result,
Predicate pred) ;
remove ( ) is the most straightforward of the four functions since it searches
a range for a value and removes all occurrences of it. remove_if () em-
ploys a predicate to identify the values that should be deleted from the sequence.
All values that satisfy the predicate are removed. The two copy functions work
analogously, but the original sequence is left unchanged and all values that do
not satisfy the predicate are copied to the destination sequence. The result is the
same-to remove the values that satisfy the predicate. remove_copy ( ) elimi-
nates all values that are equal to value, and remove_copy_if ( ) eliminates
those that satisfy its predicate. All of the functions return a past-the-end iterator
for the possibly shortened sequence. As usual, the predicate must be a function
object. (See Listing 4.20.)
Mutating Sequence Algorithms 109
#include <algorithm>
#include <list>
#include <iostream>
#include <functional>
main ()
{
list<int> listl;
list<int>: :iterator iter;
int i;
ostream_iterator<int> outstream(cout, " ");
iter = remove_if(listl.begin(),listl.end(),
bind2nd(greater<int>() ,6»;
copy(listl.begin(), listl.end(), outStream);
cout « '\n';
copy(listl.begin(), iter, outStream);
cout « '\n';
return(O);
012 3 4 567 8 9
01234 567 8 9
0123456
Although the function removed the values from the list, it did not change the
endpoint returned by end ( ). You can only determine the new endpoint by us-
ing the iterator returned by the function. Now what happens if values are deleted
from the middle of the sequence rather than the end, as in Listing 4.21?
110 4: Sequence Algorithms
00 1 :2 3 5 6 6 7 8 9
[. 1 : 2 3 5 6
If the code in Listing 4.21 is added just before the end of the previous pro-
gram, we gain an insight into how the remove algorithm actually works. To re-
move an element, it moved all the elements down, resulting in one element being
duplicated. The endpoint of the list, as returned by end ( ) , is no longer valid,
and you should be careful not to use it.
4.4.8 Unique
The unique algorithm traverses a sequence and deletes all duplicates of adjacent
elements that satisfy a condition. The algorithm runs in linear time and is imple-
mented by the functions unique and unique_copy, each of which has two
signatures:
template <class Forwardlterator>
Forwardlterator unique(Forwardlterator first,
Forwardlterator last) ;
class BinaryPredicate>
Outputlterator unique_copy (
Inputlterator first,
Inputlterator last,
Outputlterator result,
Binarypredicate binary_pred);
Since the algorithm finds duplicates only if they are adjacent, it is usually ap-
plied to sequences that are sorted. This need not always be the case, and the al-
gorithm can be applied to any sequence as long as the programmer remembers
that it will remove duplicate elements only if they are adjacent.
The first form of unique removes duplicate adjacent elements that are
equal. The second form of unique removes duplicate adjacent elements that
satisfy the predicate supplied. unique_copy performs the same operation
while making a copy of the original sequence. unique_copy removes adja-
cent duplicates during the copy operation, leaves the original sequence un-
changed, and places the possibly shorter resulting sequence at the position
referenced by resul t. All of the functions return a past-the-end iterator for the
result sequence, which should be used in place of the value returned by end ( )
for the result sequence. (See Listing 4.22.)
#include <algorithm>
#include <iostream>
#include <functional>
main ( )
{
int ar[10], i, *new_end;
ostream_iterator<int> outStream(cout, " ");
returneD);
1 2 3 4 566 6 9
1 2 3 4 569
1
1
Listing 4.22 - Deleting Duplicate Values [uniq.cpp]
The first call to unique removes all the adjacent duplicates by comparing
adjacent values for equality. The second call to unique specifies a predicate
equivalent to opera tor<, with the result that adjacent elements are considered
duplicates if the first one is less than the second. The effect of this is to remove
all of the sequence after the I, since all of the elements after that are less than the
element that succeeds them.
4.4.9 Reverse
The reverse algorithm, as the name implies, reverses the order of all elements in
a sequence. This is a linear time algorithm that is implemented by the functions
reverse ( ) and reverse_copy ( ) :
template <class Bidirectionallterator>
void reverse(Bidirectionallterator first,
Bidirectionallterator last);
#include <algorithm>
#include <string.h>
#include <iostream>
main ( )
{
char strl[16], str2[16], *iteri
ostream_iterator<char> outStream(cout, IIII)i
return(O)i
reussawwassuei
~
4.4.10 Rotate
Forwardlterator middle,
Forwardlterator last) ;
#include <algorithm>
#include <iostream>
main ( )
{
int ar[IO], i, *middle;
ostream_iterator<int> outStream(cout, " H);
return(O);
6 7 890 1 2 3 , 5
The effect is to take the original sequence and rotate it so that the element in
the seventh position is moved to the front of the sequence. The iterators for the
beginning and end of the sequence remain valid after the operation completes.
The random shuffle algorithm rearranges the elements in a sequence so that their
order is random. The resulting sequence will have a uniform distribution. The
algorithm runs in linear time and is implemented by the random_shuffle ( )
function, with two signatures:
template <class RandomAccesslterator>
void random_shuffle(
RandomAccesslterator first,
RandomAccesslterator last) ;
#include <algorithm>
#include <iostream>
main ()
{
int ar[IO], i;
ostream_iterator<int> outstream(cout, " H);
cout « I \n I ;
returneD);
}
4 3 0 2 6 7 8 9 5 1
3 9 7 5 0 2 6 8 1 4
0 3 2 6 5 8 4 9 7 1
:2 8 9 5 1 7 0 6 4 3
As you can see, the output from the function changes every time it is called.
4.4.12 Partitioning
Partitioning divides a sequence into two subsequences based upon whether the
values satisfy a predicate. All values satisfying the predicate are placed in the
first subsequence and the rest in the second subsequence.
One of the common uses of such an operation is to perform a partial sort as
part of a larger sorting algorithm. For example, the quicksort selects a value in a
sequence and partitions the sequence so that all members less than the value are
in one subsequence, and all members greater than or equal to the value are in the
other subsequence. Quicksort continues to split the sequences like this until they
are small enough to be sorted conveniently with another algorithm. The result-
ing subsequences, each of which is sorted, are then merged to form a complete
sorted sequence.
The partition algorithm runs in linear time and is implemented by the func-
tions parti tion () and stable_parti tion ( ):
template <class Bidirectionallterator, class
Predicate>
Bidirectionallterator partition(
Bidirectionallterator first,
Bidirectionallterator last,
Predicate pred) ;
Predicate>
Bidirectionallterator
stable-partition(Bidirectionallterator first,
Bidirectionallterator last,
Predicate pred) ;
Both of the functions have the same parameters and perform the same func-
tion. The difference is how they arrange the elements in the two subsequences.
stable_parti tion ( ) guarantees that the relative order of equal elements in
each of the subsequences will be preserved, whereas partition ( ) makes no
such claim.
To see how partition is used, let's write the quicksort algorithm as in Listing
4.26.
#include <algorithm>
#include <iostream>
#include <functional>
~ain ()
118 4: Sequence Algorithms
int ar[lOl, i;
ostream_iterator<int> outStream(cout, " H);
QuikSort(ar, ar+10);
copy(ar, ar+10, outStream);
cout « '\n';
return(O);
' 3 0 2 6 7 8 9 5 1
1.01 2 3 4 5 6 7 8 9
The quicksort algorithm begins by checking the length of the sequence by us-
ing the function distance ( ). If the sequence is of length 0 or 1, it returns,
since these sequences are already sorted. If the sequence is of length 2, it swaps
the values if necessary to obtain a sorted sequence. If the sequence is longer, the
quicksort partitions the sequence into two subsequences that will be sorted
recursively.
The swapping of two out of order values is where some of the sorting occurs.
The rest is performed by partitioning the sequence, which is equivalent to a par-
tial sort. Although the partition step might not seem like much, it reduces the
number of swaps that must be done and makes the algorithm quicker.
To partition the sequence, the algorithm first finds a value to use as a point to
partition the sequence, called the pivot. Although many papers have been writ-
ten on how a pivot should be selected, our quicksort program picks the first of
two unequal adjacent values in the sequence. The function adj acent-
_find ( ) is used to pick a pivot by passing a predicate of not_equal_to ( ).
Once the pivot has been selected, the parti tion ( ) function splits the se-
quence into one where all the values are less than the pivot, and a second where
all the values are greater than or equal to the pivot. QuikSort ( ) is then called
recursively to sort each of these subsequences.
Mutating Sequence Algorithms 119
Even a short program such as quicksort can make use of several of the STL
functions. As you become more familiar with the functions that are available,
you will find more applications for them in your daily programming tasks.
5
Sorting and Related Algorithms
5.1 Introduction
The STL sorting and related algorithms include sorting, searching, merging, set
operations, heap operations, maximum, minimum, and lexicographical compari-
son. These operations are either involved in sorting or manipulating sequences
that are assumed to be sorted in some way. The set and heap operations actually
create new data structures from existing ones by providing a set of operations to
operate on the new structures. Taken together, the sorting and related algorithms
form a major portion of the total STL algorithms.
5.2 Preliminaries
The algorithms in this chapter are all concerned with the ordering of elements in
sequences. To perform these algorithms, it is necessary to compare elements so
that an order of the elements can be established. This comparison is done in two
different ways: using opera tor< or a function object of type Compare. A
function object of type Compare must provide opera tor ( ) (a, b), which
returns true only if the first parameter is less than the second if it is to behave
like opera tor<.
Most of the time the algorithms deal with sequences of classes or built-in
types for which operator< is defined. A function object of type Compare
can be used for those types that do not have opera tor< defined or when a
special-purpose ordering is desired that differs from that provided by opera-
tor<. All of the algorithms that follow have an implementation using opera -
tor< and an implementation that uses a function object of type Compare.
5.3 Sorting
Sorting is one of the most common operations performed on computers with
some studies reporting that 75 percent of a computer's time is spent sorting. As
a result, it is important that the sorting algorithms used be as efficient as possi-
ble. The STL provides several sorting functions that run in O(n log n) time.
These are some of the most efficient general-purpose sorting algorithms and are
suitable for use in most applications. The sorting algorithm is implemented by
the functions sort ( ), stable_sort ( ), partial_sort ( ), and part-
ial- _sort_copy ( ):
template <class RandomAccesslterator>
void sort(RandomAccesslterator first,
RandomAccesslterator last);
Inputlterator last,
RandomAccesslterator result_first,
RandomAccesslterator result_last) ;
#include <algorithm>
#include <iostream>
#include <functional>
} i
maine )
{
int ar[20], ii
ostream_iterator<int> outstream(cout, " ")i
return(O)i
Sorting 125
unsorted: 6 11 9 2 18 12 17 7 0 15 4 8 10 5 1 19 13 3 14 16
Ascending: o 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Unsorted: 18 15 4 5 8 3 17 13 10 16 9 11 6 1 14 19 2 0 12 7
Descending: 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
#include <algorithm>
#include <iostream>
#include <functional>
struct Person {
char* name;
int age;
};
class PersonCmp:
public binary_function<Person&, Person&, bool>
{
public:
bool operator() (Person& pI, Person& p2)
{
return(pl.age < p2.age);
126 5: Sorting and Related Algorithms
};
main ( )
{
Person folks[lD] =
{ { "John", 8}, {" Sue", l2}, { "Sam", 7},
{"Ellen", 9}, {"Cathy", 8}, { "Bobby", 9},
{"Pete", II}, {"Jane", 7}, {"Dick", 8},
{"Tammy", II}};
ostream_iterator<Person> outstream(cout, " ");
sort(folks, folks+1D,PersonCmp(»;
copy(folks, folks+5, outStream);
cout « endl;
copy(folks+5, folks+1D, outStream);
cout « endl;
returneD);
#include <algorithm>
#include <iostream>
#include <functional>
struct Person {
char* name;
int mark;
int operator«Person p2) { return(mark > p2.mark);}
};
return os;
class PersonCmp:
public binary_function<Person&, Person&, bool>
{
public:
PersonCmp() {}
bool operator() (const Person& pI, const Person& p2)
const
};
void maine)
{
Person folks[IO] =
{{"John", 92}, {"Sue", 61}, { "Sam", 73},
{"Ellen", 88},{"Cathy", 86}, { "Bobby", 79 } ,
{"Pete", 57}, {"Jane", 78}, {"Dick", 63},
{ "Tammy", 74}};
Person* middle;
ostream_iterator<Person> outStream(cout, " ") i
middle = folks + 5;
partial_sort(folks, middle, folks+IO, PersonCmp(»;
copy(folks, middle, outStream);
cout « endl;
128 5: Sorting and Related Algorithms
(John, 92) (Ellen, 88) (Cathy, 86) (Bobby, 79) (Jane, 78)
The nth element algorithm places a single element from a sequence into the posi-
tion in which it would lie had the entire sequence been sorted. It also ensures
that all of the elements before the final position of the nth element are less than or
equal to all of the elements after the nth element. This is a linear time operation
and is implemented by the function nth_element ( ):
template <class RandomAccesslterator>
void nth_element (
RandomAccesslterator first,
RandomAccesslterator nth,
RandomAccesslterator last);
#include <algorithm>
#include <iostream>
main ( )
{
int ar[lO], ii
ostream_iterator<int> outstream(cout, " ")i
for(i=Oii<lOii++) ar[i] = ii
random_shuffle(ar, ar+10)i
copy(ar, ar+10, outstream)i
cout « endli
return(O)i
'0 3 0 2 6 7 8 9 5 1
[. 1 2 3 4 567 8 9
The important point to notice here is that nth_element ( ) places the ele-
ment that will lie at the sixth position (counting in origin 1) of the sorted se-
quence into its final position. It does not necessarily move the sixth element, in
this case, 7, of the unsorted sequence into its final position.
The binary search algorithm is a fast search algorithm for sorted sequences. It
takes logarithmic time and is used as the search algorithm by all the other algo-
rithms in this section. The binary search algorithm itself is used to find a spe-
cific value in the sequence. The algorithm is implemented by the function
binary_search ( ):
template <class Forwardlterator, class T>
bool binary_search(FOrwardlterator first,
Forwardlterator last,
const T& value);
t
top
+
1214 15 17 18 19 111 1141171
/"
bot probe top
~ + ~
1214 15 17 18 19 111 1141171
#include <algorithm>
#include <iostream>
void main()
{
int ar[10] = {I, 3, 4, 5, 7, 8, 9, 11, 14, 15];
int found = 0;
46 is present
1. is absent
The lower bound algorithm searches a sorted sequence for the first element that
is greater than or equal to a specified value. This is the first position into which
the value can be inserted without violating the sequence order. It returns an it-
erator that references this element or a past-the-end iterator if such an element
does not exist. The algorithm runs in logarithmic time and is implemented by
the function lower_bound ( ):
template <class Forwardlterator, class T>
Forwardlterator lower_bound (
Forwardlterator first,
Forwardlterator last,
const T& value);
Forwardlterator lower_bound (
Forwardlterator first,
Forwardlterator last,
const T& value,
Compare comp) ;
Listing 5.6 searches a sorted sequence to find the lower bound of the subse-
quence of values greater than or equal to 5.
#include <algorithm>
#include <iostream>
void maine)
{
int ar [10], i, * iter;
ostream_iterator<int> outstream(cout, " H);
for(i=0;i<10;i++) ar[i] = i;
iter = lower_bound(ar, ar + 10, 5);
if(iter == ar + 10) cout « "Not found";
else copy(iter, ar+10, outstream);
cout « endl;
156789
#include <algorithm>
#include <functional>
#include <iostream>
void main()
{
int ar[lO], i, *iter;
ostream_iterator<int> outstream(cout, " H);
for(i=O;i<lO;i++) ar[i] = 9 - i;
iter = lower_bound (ar, ar + 10, 5,greater<int>(»;
if(iter == ar + 10) cout « "Not found";
else copy(iter, ar+l0, outstream);
cout « endl;
15 4 3 2 1 0
The upper bound algorithm finds the first value in a sorted sequence that is
greater than a specified value. This can also be viewed as the last position into
which the value can be inserted without violating the ordering of the sequence.
The algorithm is implemented by the function upper_bound ( ):
template <class Forwardlterator, class T>
Forwardlterator upper_bound (
Forwardlterator first,
Forwardlterator last,
Searching Algorithms 135
#include <algorithm>
#include <list>
#include <iostream>
void main ( )
{
int i;
list<int> listl;
list<int>: :iterator iter;
ostream_iterator<int> outstream(cout, " H);
for(i=O;i<S;i++) listl.push_back(i);
for(i=10;i<15;i++) listl.push_back(i);
copy(listl.begin(), listl.end(), outstream);
cout « endl;
iter = upper_bound(listl.begin(), listl.end(), 7);
copy(iter, listl.end(), outStream);
cout « endl;
listl.insert(iter, 1, 7);
copy(listl.begin(), listl.end(), outStream);
cout « endl;
136 5: Sorting and Related Algorithms
o 1 2 3 4 10 11 12 13 14
10 11 12 13 14
o 1 2 3 4 7 10 11 12 13 14
Although the list is created in sorted order, a gap has been left in the middle
to allow for the insertion of one or more values. The call to upper_bound ( )
returns a reference to the element in front of which a 7 can be inserted without
disrupting the overall order of the sequence. It does not matter whether the value
that upper_bound () is looking for is actually in the sequence, since it only
has to find the first value greater than it. The iterator returned by up-
per_bound () is used directly in the insert () function and results in the
proper insertion of the value 7 into the list.
The algorithm also works correctly if it is asked to find the upper bound on a
value that is beyond the limits of the existing sequence. For example, if you
tried to find the upper bound of 20 in a sequence from 0 ... 14, the past-the-end
iterator would be returned. Using this as the position to insert the value into the
list would insert the value at the end of the list, the correct position.
The equal range algorithm searches a sorted sequence for a range of values that
are all equal to a specified value. This implies that the specified value could be
inserted at any point in the resulting range. The algorithm is implemented by the
function equal_range ( ):
template <class Forwardlterator, class T>
pair<Forwardlterator, Forwardlterator>
equal_range (FOrwardlterator first,
Forwardlterator last,
const T& value);
Forwardlterator last,
const T& value,
Compare comp) ;
equal_range () searches the sorted sequence from first up to last
for a range of elements equal to value. If such a range exists, it returns a pair
of iterators delimiting the range. If no such range exists, it returns a pair of itera-
tors, both of which are equal to first.
The pair of iterators is returned as a structure designed for this purpose,
called pair. A pair consists of two values called first and second. The
rationale for using such a data structure is that it allows two values to be moved
around as easily as a single value. The declaration for the structure looks like
this:
template <class Tl, class T2>
struct pair {
Tl first;
T2 second;
pair();
pair(const Tl& a, const T2& b);
template<class R, class s>
pair(const pair<R, S>& p);
};
#include <algorithm>
#include <list>
#include <iostream>
void maine)
{
list<int> listl;
int i;
ostream_iterator<int> outstream(cout, " ");
pair<list<int>: :iterator, list<int>: :iterator>
range;
138 5: Sorting and Related Algorithms
[~ ~ 22' , 6 6 8 8
One point to keep in mind is that the second member of the pair returned is a
past-the-end iterator for the subsequence, not a reference to the last member of
the subsequence. It is also worth noting how the pair range is declared.
5.4.5 Merge
The merge algorithm merges two sorted sequences to yield a third sorted se-
quence that contains all of the elements from the first two sequences. This algo-
rithm usually runs in linear time but might be O(nlogn) if memory is not
available. The algorithm is stable-that is, the relative order of equal elements in
the two input sequences is maintained in the final sequence. The algorithm is
implemented by the functions merge ( ) and inplace_merge ( ):
template <class Inputlteratorl, class Inputlterator2,
class Outputlterator>
Outputlterator merge(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
Outputlterator result);
#include <algorithm>
#include <iostream>
void main ()
{
int inl[lOj, in2[lO],
result[20j, *end, i;
ostream_iterator<int> outstream(cout, " H);
inl[i] i * 3;
in2[i] i * 2;
0 3 6 9 12 15 18 21 24 27
0 2 4 6 8 10 12 14 16 18
0 0 2 3 4 6 6 8 9 10 12 12 14 15 16 18 18 21 24 27
inplace ---
o 3 6 9 12 15 18 21 24 27 0 2 468 10 12 14 16 18
o 0 2 3 4 6 6 8 9 10 12 12 14 15 16 18 18 21 24 27
sets and restrict the cardinality to the number of bits in a word on the architecture
on which they are implemented. Such implementations also preclude the storage
of duplicate values in sets. The STL implementation has neither of these restric-
tions, although it will not have the performance of a bitmap implementation.
There is a possibility of confusing terms when using sets, since the STL pro-
vides two associative containers called the set and mul tiset. These are de-
signed for the fast storage and retrieval of objects and have little to do with the
implementation of mathematical sets as described in this section. Mathematical
sets are created by using the set algorithms, not by using a particular container.
5.5.1 Includes
The includes algorithm determines if all the members of one set are contained in
a second set and returns a Boolean result. One set is defined to be a subset of
another if the number of occurrences of each element in the first set is less than
or equal to the number of occurrences in the second set. The algorithm runs in
linear time and is implemented by the function includes ( ):
template <class InputIteratorl, class InputIterator2>
bool includes(InputIteratorl firstl,
InputIteratorl lastl,
InputIterator2 first2,
InputIterator2 last2);
#include <algorithm>
#include <iostream>
void main ( )
{
int setl[5] {l, 2, 3, 4, 5};
int set2[5] {2, 3, 3, 4, 5};
int set3[6] {2, 3, 3, 3, 4, 5} ;
int set4[2] {2, 5} ;
set2 is not a subset of setl since the repeated value 3 occurs more times
in set2 than it does in setl. For the same reason, set3 is not a subset of
set2, but set2 is a subset of set3 since set2 has only two 3's, whereas
set3 has three 3'So Finally, set4 is a subset of setl since all of the values in
set4 occur in setl.
Set Algorithms 143
The set union algorithm performs the union of two sets or multisets
represented as sorted sequences. If duplicate values occur in either
of the input sequences, then the result sequence will contain the
duplicate value the maximum of the number of times the value is
duplicated in either of the input sequences. For example, if the
' - - - - - - -- - - - " first sequence contains one 3 and the second sequence contains
three 3s, then the result will contain three 3s. If the same value oc-
curs in the two input sets, then the value from the first input set
will be copied to the result. The algorithm takes linear time and is
implemented by the function set_union ( ):
template <class Inputlteratorl, class Inputlterator2,
class outputlterator>
outputlterator set_union(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
outputlterator result);
#include <algorithm>
#include <iostream>
void main()
{
int setl[5] = {2, 3, 3, 4, 5};
int set2 [6] = {2, 3, 3, 3, 4, 5};
int result[15] , *end;
ostream_iterator<int> outstream(cout, " ");
[ 2 3 334 5
Because the value 3 is duplicated in one of the input sequences, the result se-
quence contains a total of three 3' s since that was the maximum number of times
it was duplicated in either of the input sequences. This is the definition applied
to multisets. If set_union ( ) is applied to sets as opposed to muItisets, it will
perform a normal set union operation.
To get a better idea of how duplicates are handled, we need to create a set
containing instances of either structures or classes. (SeeListing 5.13.) This will
allow us to differentiate between records that have the same key so that we can
see which values end up in the result.
Set Algorithms 145
#include <algorithm>
#include <iostream>
struct Person {
char name[l6];
int age;
};
void maine)
{
Person setl[3] = {{"George", 19}, {"Sam", 20},
{"Marty", 23}};
Person set2 [4] = {{ "Mary", l8}, {"Susan", 20},
{"Sonya", 20}, {"Gail", 2l}};
Person result[lO], *end;
Mary, 18
George, 19
Sam, 20
Sonya, 20
Gail, 21
Marty, 23
Mary, 18
George, 19
Susan, 20
Sonya, 20
Gail, 21
Marty, 23
The two input sets contain a total of three people of age twenty-one in the
first set and two in the second. The definition of a union of sets containing du-
plicates states that the result will repeat the duplicated element the maximum
number of times it occurs in either input set. This is two, so obviously one of the
input values has to be dropped.
The set union algorithm works by comparing a value from the first set with a
value from the second set. If these two values are the same, only one of them
needs to appear in the result, and the algorithm copies the one from the first in-
put set. The previous example performs the union of setl and set2 followed
by the union of set2 and setl. In the first case, it compares Sam to Susan
and copies Sam, whereas in the second union, it compares Susan to Sam and
copies Susan.
Set Algorithms 147
#include <algorithm>
#include <iostream>
void maine)
{
int setl [6] = {2, 3, 3, 4, 5, 9};
int set2 [ 6] = {2, 3, 3, 3, 4, 5};
int result[l5], *end;
148 5: Sorting and Related Algorithms
The important point to notice here is that the value 3 occurs twice in the re-
sult-the minimum number of times it occurs in either of the input sets.
returned. The result sequence should not overlap either of the input sequences,
as shown in Listing 5.15.
#include <algorithm>
#include <iostream>
void maine)
{
int setl [6] = {2, 3, 3, 4, 5, 9};
int set2 [6] = {2, 3, 3, 3, 4, 5};
int result[l5], *end;
ostream_iterator<int> outstream(cout," ");
9
3
The interesting point in this example is that set2 - setl returns a 3, and
setl - set2 does not. Since 3 is a duplicate value, a 3 is removed from the
first sequence every time one is encountered in the second sequence. Thus,
set2 - setlleaves one 3, whereas the reverse deletes a1l3's.
150 5: Sorting and Related Algorithms
#include <algorithm>
#include <iostream>
void maine)
{
int setl [6] = {2, 3, 3, 4, 5, 9};
int set2 [6] = {2, 3, 3, 3, 4, 5};
int result[15] , *end;
ostream_iterator<int> outStream(cout, II " ) ;
2 3 4 5 6 7 8 9 10
Several properties make the heap a useful data structure. Since each node is
greater than either of its children, the largest value in the heap must be at the
root. This makes it convenient for applications that frequently want to access the
greatest value in a set of values. Further, since it is a tree, if the root is removed,
a heap can be remade into a tree by moving values up from lower levels in
O(logn) time. A new value can also be inserted in O(logn)time. This not only
makes it convenient for many applications, but efficient as well.
Insertion into a heap is accomplished by placing the new node as the leftmost
child of the lowest level of the tree. Since this is rarely the correct position for
the node in the heap, it must be moved to the correct position. To do this, the
node is repeatedly exchanged with its parent until either its parent is greater than
it, or it reaches the root.
This procedure is illustrated in Figure 5.3, where the value 11 is inserted into
an existing heap. The tree at the top left shows the new value being inserted as
the leftmost child at the lowest level. Since it is greater than its parent, it is ex-
changed with its parent, as shown in the tree at the top right. It is still greater
than its parent, so it is exchanged once again to yield the tree in the lower portion
of the diagram. At this point, the tree has been restored to have the properties of
a heap, and the insertion is complete.
Deletion from a heap is performed by removing the root and reforming the
resulting two trees into a heap. It makes sense to remove the root since that is
the value of interest in most applications of heaps. The two trees are reformed
into a heap by selecting a new root as the larger of the roots of the two subtrees.
This leaves one of the subtrees without a root so the larger of its two children is
Heap Algorithms 155
promoted to the position of root. This process continues until a leaf is promoted
to become the root of a subtree.
Deletion from a heap is shown in Figure 5.4, where the root is removed from
the final heap of the previous diagram. Once the root is removed, node 12 is
promoted to be the new root, leaving its subtree without a root. Node 4 is pro-
moted to this position and one leaf is lost from the tree.
This is all well and fine for dealing with trees, but the STL algorithms deal
with sequences. This anomaly can be cured if we can represent a tree as a se-
quence. Fortunately, such a technique exists for a class of trees called almost
complete binary trees.
An almost complete binary tree is one where every node is either a leaf or
has two children except the rightmost node at the level one above the bottom,
which is allowed to have a single child. Any tree with these properties can have
its nodes numbered sequentially from the root downward in left-to-right order.
The values in the nodes can then be stored in a sequence based on the numbering
of the nodes.
A sequence such as this has one very interesting property: to find the position
of the left child of any node, you simply double the value of its position in the
sequence. For example, in Figure 5.5, the left child of node 17 in position 1
must be in position 2, and the left child of node 5 in position 4 must be in posi-
tion 8. The right child of any node can be found by multiplying the node's posi-
tion by 2 and adding 1. To find the parent of a node, divide its position by 2 and
round down the result.
This technique allows an almost complete binary tree to be stored in a se-
quence. Fortunately, a heap can always be maintained as an almost complete bi-
nary tree and can thus always be stored in a sequence. This allows the STL to
treat any sequence as a heap as long as the appropriate algorithms are used.
Several functions are provided to create and manipulate heaps represented as
sequences. The most basic of these functions is make_heap ( ), which creates
a heap from an unsorted sequence:
template <class RandomAccesslterator>
void make_heap(
RandomAccesslterator first,
RandomAccesslterator last) ;
Compare comp) ;
make_heap () rearranges the elements in the range from first up to
last so that they form a heap. This is an O(n) operation that must be per-
formed before any of the subsequent heap operations can be used. Once a heap
has been created, elements can be inserted into the heap using push_heap ( )
and removed from the heap using pop_heap ( ):
template <class RandomAccesslterator>
void push_heap(
RandomAccesslterator first,
RandomAccesslterator last);
The functions max ( ) and min ( ) return the largest and smallest of two ele-
ments, respectively. The functions max_element () and min_element ( )
return iterators that reference the largest and smallest elements in a sequence.
Listing 5.17 shows how min_element ( ) can be used to implement a hor-
ribly inefficient O(n2) sort. Take this as an example of how not to write a sort.
#include <algorithm>
#include <iostream>
void main ( )
{
int ar[10] = {S, 3, 5, 2, 0, 9, 1, 7, 6, 4]i
int i, *iteri
cout « endli
10 1 2 3 4 5 6 7 8 9
This is actually one of the simplest sorting algorithms. The for loop is per-
formed once for every element in the sequence and finds and removes the mini-
mum element each time. Since the sequence is shortened by one element each
time an element is removed, it guarantees that the correct range is searched as
the values are removed.
160 5: Sorting and Related Algorithms
The function returns a Boolean value of true if the sequence from firstl
up to lastl is lexicographically less than the sequence from first2 up to
las t 2. If this is not the case, then fa 1 s e is returned. The second form of the
function permits the specification of a comparison function other than opera-
tor<. This is useful for defining another order for the comparison or when
user-defined types are involved. (See Listing 5.18.)
#include <algorithm>
#include <iostream>
void maine)
{
char serial[2] [7] {"03963A", "03963D"};
bool result;
5.7.3 Permutations
021
102
120
201
210
As you can see, these sequences are all different permutations of the original.
Further, they are lexicographically sorted to form an ordered set of sequences.
The STL permutation functions assume that any sequence passed to them is a
member of the sorted set and can generate either the next or previous member of
the set.
The number of possible permutations for a sequence of length n is n! , where
the exclamation point indicates the factorial function. The factorial of an integer
n is defined as n X (n -1) X (n - 2) ... xl, so the factorial of 3, denoted as 3!, is
defined as 3 x 2 x 1 =6. This explains the six permutations of the three elements
in the previous example. Be aware that factorials increase very quickly, as
shown in Table 5.1.
One of the uses of permutations is in generating all possible arrangements of
elements in a brute force algorithm. Such algorithms solve a problem by trying
all possible permutations of a sequence until they fmd one that works. Although
this approach is not elegant, it is the only known solution for some problems.
Such algorithms should be avoided whenever possible due to the rapid explosion
in the number of permutations as the length of the sequence increases.
O! 1
1! 1
2! 2
3! 6
4! 24
5! 120
6! 720
7! 5,040
10! 3,628,800
15! 1,307,674,368,000
#include <algorithm>
#include <iostream>
using namespace std;
void maine)
{
int ar [4], i;
bool exists;
ostream_iterator<int> outstream(cout, " H);
for(i=O;i<4;i++) ar[i] = i;
copy(ar, ar+3, outStream);
cout « endl;
while (exists) {
copy(ar, ar+3, outstream)i
cout « endl;
exists = next_permutation (ar, ar+3);
0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0
after last permutation
012
The program begins with a sorted sequence and then repeatedly calls next-
_permutation ( ) until it returns false, indicating that no further permuta-
tions exist. The final call to next_permutation() returns false and
reverses the order of the elements in the sequence, yielding the original
permutation.
6
Generalized Numeric Algorithms
6.1 Introduction
This chapter describes algorithms for common numeric operations on sequences
including calculating running sums, inner products, and the difference between
adjacent elements. It also describes the numeric array-an array that permits op-
erations on all members of an array at once and provides sophisticated subscript-
ing methods. Although there are fewer members of this category of algorithms,
their common usage makes them an important component of the STL.
6.2 Accumulation
The accumulate algorithm is similar to the APL reduction operator. It takes a se-
quence of values and an operator, places the operator between every pair of val-
ues in the sequence, and evaluates the resulting expression. The result is what is
returned from evaluating the expression. This is a linear time algorithm and is
implemented by the function accumulate ( ):
template <class Inputlterator, class T>
T accumulate(Inputlterator first,
Inputlterator last,
T init);
Both forms of the function require an initial value, which will be placed at
the start of the sequence before the operator is inserted. In the case where a se-
quence of zero length is passed to the functions, they will return the initial value,
in it. The first form of the function inserts opera tor+ between the elements,
and the second form inserts binary _op between the elements, as in Listing
6.1.
#include <numeric>
#include <functional>
#include <iostream>
void main()
{
int ar[5], i, sum;
ostream_iterator<int> outStream(cout, " H);
for(i=O;i<S;i++) ar[i] = i + 1;
copy(ar, ar+5, outStream);
cout « endl;
c
1 2 3 4 5
.
+: 15
-15
: 120
sequence, operator- yields the negative sum, and operator* the product.
The actual expressions that are evaluated are as follows:
o+ 1 + 2 + 3 + 4 + 5 15
o - 1 - 2 - 3 - 4 - 5 -15
1 * 1 * 2 * 3 * 4 * 5 120
The use of operator* requires an initial value of 1 rather than zero; other-
wise, the result would always be zero.
Inputlterator1 last1,
Inputlterator2 first2,
T init,
BinaryOperation1 binary_op1,
BinaryOperation2 binary_op2);
Both forms of the function allow an initial value to be specified that is pre-
pended to the start of the intermediate sequence before the accumulation is per-
formed. If an initial value of 10 had been used with the preceding example, the
following accumulation would have been performed:
10 + 6 + 16 + 30 + 48 = 110
The second form of the function allows the specification of operators other
than operator* and operator+, as are used by the first form of the func-
tion. binary _op2 is used to perform the element-by-element operation, and
binary_op1 is used to perform the accumulation. This is demonstrated in
Listing 6.2.
#include <numeric>
#include <functional>
#include <iostream>
for(i=O;i<S;i++) {
ar1[i] i;
ar2[i] = i + 2;
01234
23456
x+: 50
-+: 5
sequence 2 : 2 3 4 5 6
U U U U U
intermediate: 15 + -2 + -2 + -2 + -2 + -2 = 5
The initial value is prepended to the intermediate sequence before the accumula-
tion is performed.
The function calculates a running total of the elements in the sequence from
first up to last and places the resultant sequence at the location referenced
by result. The past-the-end iterator for the result sequence is returned, as
shown in Listing 6.3.
#include <numeric>
#include <functional>
#include <iostream>
void maine)
{
int ar[10], result[10], i, *end;
ostream_iterator<int> outstream(cout, II " ) ;
for(i=O;i<10;i++) ar[i] = i *i + 1;
1 2 5 10 17 26 37 50 65 82
1 3 8 18 35 61 98 148 213 295
1 -1 -6 -16 -33 -59 -96 -146 -211 -293
sequence of the same length as the original. This linear time algorithm is imple-
mented by the function adj acent - _difference ( ):
template <class Inputlterator, class Outputlterator>
Outputlterator adjacent_difference (
Inputlterator first,
Inputlterator last,
outputlterator result);
#include <numeric>
#include <functional>
#include <iostream>
void maine)
{
int ar[10j, resu1t[10j, i, *end;
ostream_iterator<int> outstream(cout, II " ) ;
for(i=O;i<10;i++) ar[ij = i *i + 1;
cout « endl;
1 2 5 10 17 26 37 50 65 82
1 1 3 5 7 9 11 13 15 17
+: 1 3 7 15 27 43 63 87 115 147
#include <valarray>
#include <iostream>
void main ( )
{
int arl [l = {I, 2, 3};
int ar2 [l = {2, 5, 6};
const size_t AR_SIZE = 3;
size_t i;
valarray<int> vl(arl, AR_SIZE);
valarray<int> v2(ar2, AR_SIZE);
vI += 2;
for(i = 0; i < AR_SIZE; i++)
cout « vl[il « " ";
cout « endl;
vI += v2;
Numeric Arrays 173
I~ :~1
Listing 6.5 - valarray Operations [valarl.cpp]
valarray also defines several different ways the array can be subscripted.
These include subscripting a single element, a group of arbitrary elements, and a
group of elements spaced a fixed distance apart.
While valarray can contain any type, most of the operations defined on it
are only applicable to numeric types. If the operation performed on a valar-
ray is to succeed, the operation must be defined for the type stored in the
valarray.
Although valarray is a container, it is not a container in the same sense as
the other STL containers. While you might think it is a sequence container, it
does not satisfy the requirements of a sequence container. It does not provide an
iterator or the methods beg in ( ) and end ( ). The valarray is best thought
of as a special type of array rather than as one of the STL containers.
6.6.1 Subscripting
contain valid subscripts. The exact type returned depends on whether the result
of operator [] is used as an I-value or an r-value.
When the result of operator [] is used as an r-value, a valarray of the
expected length is returned. If the result of operator [] is used as an I-value,
an instance of indirect_array is returned. indirect_array is a class
that contains a reference to the array that was subscripted as well as the valar-
ray used as an argument to operator [].
The class indirect_array defines the same operators as does val-
array. As a result, the indirect_array behaves identically to a valar-
ray, except only the elements specified by the subscript are affected. To see
how this works, consider Listing 6.6:
#include <valarray>
#include <iostream>
void maine)
{
i, sub_ar [] = {I, 3, 5};
da ta_ar [] = { 1 , 2, 3 , 4, 5, 6,
11 3 3 6 5 9 7
The valarrays are created from the corresponding regular arrays. Sub-
scripting data_val with sub_val returns an instance of indirect_ar-
ray, which references data_val and has the subscripts sub_val. The
member operator+= of indirect_array is then used to perform the ad-
dition so that only the members specified by the subscripts are affected. This
same technique is used with the slice_array, gslice_array, and
mask_array described below. None of these classes can be instantiated di-
rectly-they are only created as a result of subscripting.
valarray is a one-dimensional array. This poses a problem for applica-
tions that require higher-dimensional arrays. Fortunately, it is a simple matter to
use a one-dimensional array to represent a multi-dimensional array. Mapping a
multi-dimensional array onto a one-dimensional array is made even simpler by
using the class slice.
The class slice has this constructor:
slice(size_t start, size_t len, size_t stride);
start is the starting subscript, len is the number of SUbscripts, and stride
is the increment to add to the starting subscript to generate a total of len sub-
scripts. If this class is constructed properly, it can represent a slice through a
multi-dimensional array. To see how this works, we will begin by examining
how to store a two-dimensional array as a valarray.
Examining the one-dimensional array in Figure 6.1, we see that these are indeed
the correct subscripts of all members of column 1.
The same idea can be used to generate the subscripts for all members of a
row in a two-dimensional array. The slice to generate the subscripts for all mem-
bers of row rawNurn is constructed as follows:
sliee(rawNurn * neals, neals, 1);
The starting index is the number of columns multiplied by the desired row,
since each row contains neals members. The number of subscripts to generate
is neals, the number of members of a row. The stride is set to 1 since each
member of a row is adjacent to the next.
IAIBlclDIEIFIGIHlllJIKILI
o 1 2 3 4 5 6 7 8 9 10 11
o~ 0@I8]JJ nrows=2
ncols = 3
1@JI0 1~ nplanes =2
012 012
plane 0 plane 1
#include <valarray>
#include <iastream>
#include <cctype>
void main()
{
canst int nraws = 3;
canst int ncals = 4;
valarray<int> ar(nraws * ncols);
valarray<size_t> val_subscr(3);
valarray<int> result3(3);
slice slice_subscr(1,nraws, ncols);
boo I boal_vect [ ]
{false, false, false, true,
false, false, true, false,
false, true, false, false};
valarray<baol> baa1_subscr(baal_vect, 12);
int i;
ar [i] = i;
va1_subscr[0] 2;
val_subscr[1] 7;
val_subscr[2] 10;
ar[slice_subscr] = 84;
cout « endl « Oar [*, 1] = 84 -> ";
for(i = 0; i < (nrows * ncols); i++)
{
cout « ar[i] « " ";
cout « endl;
ar[2, 7, 10] - 2 7 10
ar [~, 7, 10] - 42 -> 0 1 42 3 4 5 6 42 8 9 42 11
ar [*, 1] - 84 -> 0 84 42 3 4 84 6 42 8 84 42 11
ar[bool_subscr] - 3 6 84
Numerous methods and functions are defined for use with valarray. The pro-
gram in Listing 6.8 demonstrates the use of some of these.
180 6: Generalized Numeric Algorithms
#include <valarray>
#include <iostream>
#include <algorithm>
#include <cmath>
int fn(int n)
{
return (n - 7) * 2;
}
void maine)
{
valarray<int> ar(lO);
valarray<int> arl(lO);
valarray<int> result(lO);
int i;
result = ar * 2;
result = ar + arl;
cout « endl « "ar + arl " ,.
Numeric Arrays 181
result = ar.shift(3);
cout « "ar.shift(3) = ";
for(i = 0; i < 10; i++)
{
cout « result[i] « " ";
result = ar.shift(-2);
cout « endl « "ar.shift(-2) " ,.
for(i = 0; i < 10; i++)
{
cout « result[i] « " ";
result = ar.cshift(3);
cout « endl « "ar.cshift(3) " ,.
for(i = 0; i < 10; i++)
{
cout « result[i] « " ";
}
result = ar.apply(fn);
cout « endl « "ar.apply(fn) " ,.
for(i = 0; i < 10; i++)
{
cout « result[i] « " ";
result += ar;
cout « endl « "result += ar " ,.
for(i = 0; i < 10; i++)
{
cout « result[i] « " ";
cout « endl;
ar = 0 1 2 3 4 5 6 7 8 9
arl - 9 8 7 6 5 4 3 2 1 0
ar * 2 = 0 2 4 6 8 10 12 14 16 18
ar + ar1 a 9 9 9 9 9 9 9 9 9 9
ar.max() .. 9
ar. sumO a 45
ar.shift(3) - 3 4 5 6 7 8 9 0 0 0
ar.shift(-2) - 0 0 0 1 2 3 4 5 6 7
ar.cshift(3) - 3 4 5 6 7 8 9 0 1 2
ar.apply(fn) - -14 -12 -10 -8 -6 -4 -2 0 2 4
pow(ar, 2) - 0 1 4 9 16 25 36 49 64 81
pow(ar, ar1) - 0 1 128 729 1024 625 216 49 8 1
result +- ar - 0 2 130 732 1028 630 222 56 16 10
The program begins by declaring three integer val arrays of ten members
each. It then initializes ar and ar1 and prints their contents. Next, ar is multi-
plied by the constant 2 and assigned to result, which is then printed. ar is
added to ar1 and assigned to result, showing that arrays of the same length can
be used with an operator. The methods max ( ) and sum ( ) are used to find the
maximum and sum of the values in ar, respectively. The shift ( ) method can
shift the values in either direction, with zeroes added to fill the vacated positions.
The cshift () method performs a circular shift so that the elements that spill
off one end of the array reappear at the other end. a pp 1 Y( ) applies a function,
in this case, f n, to every member of the array.
7
Sequence Containers
7.1 Introduction
This is the ftrst of two chapters discussing containers. A container is a data
structure that contains or stores instances of other data structures or objects.
Containers make it much easier to move groups of objects around, since several
objects can be referred to by a single name and manipulated as a group. Con-
tainers do more than just store a group of objects-they determine how objects
are stored and how they can be accessed and retrieved.
Containers are divided into two broad categories-sequence containers and
associative containers. The sequence containers store objects in a sequential
manner. Objects in sequence containers can be accessed either sequentially or
randomly, depending on the nature of the container. Associative containers as-
sociate a key value with every object stored in the container. A key is like a
name and can be used to retrieve a value from an associative container. Nor-
mally, associative containers arrange the keys so that they form an index for the
objects stored in the container. This results in much faster retrieval of an object
than is possible with a sequence container.
Some of the associative containers separate the data being stored from the in-
dex. The index only contains the key that identiftes the data object and is ar-
ranged so that it can be searched as quickly as possible. Each key in the index
has a reference to the data object associated with it so that once the key is found,
the data object can be retrieved by following the reference.
Sequence containers are much simpler, as they store only the data objects and
arrange them in a linear fashion. Data objects are referenced in a straightforward
manner without the use of an index. Although this dispenses with the need for
additional memory to store the index, it can dramatically increase retrieval times
for some applications.
are to be stored once and retrieved frequently in no particular order, the list
would not be a good choice of container. Retrieving from a list in random order
will, on average, require that half the list be searched each time an element is re-
trieved. If the list contains a million objects, this will result in a program that
runs very slowly. In this case, one of the associative containers that has an index
would be a more appropriate choice.
The STL also provides container adaptors. These alter the interface of a con-
tainer to create a new container. The container adaptors include the stack,
queue, and priority _queue. These are discussed in Chapter 9.
Expression Explanation
c: : value_type The type stored in the container.
c: : reference A reference to the type stored in the container.
c: :const_reference A constant reference to the type stored in the container.
c: : iterator An iterator for the container.
c: :const_iterator A constant iterator for the container.
c: : difference_type A signed integral type that can represent the difference in two
iterators.
c: : size_type An unsigned integral type that can represent any valid number of
container members.
C x Creates an empty container.
CO Creates an empty container.
C(y) Creates a copy of y.
c x(y) Creates x as a copy of y.
c x = Y Creates x and initializes it to have the value of y.
x. -C() The destructor is applied to every element in the container and all
memory is deallocated..
x .begin() Returns an iterator referencing the fIrst element in the container.
x. end() Returns a past-the-end iterator for the elements in the container.
x.sizeO Returns the current number of elements stored in the container.
x.max_size() Returns the largest possible number of elements that can be stored
in the container.
x.empty( ) True if no elements are in the container.
x == Y True if all elements in the two containers are equal.
x 1= Y True if not all the elements in the two containers are equal.
x = y Assigns the value of the container y to the container x by perform-
ing a deep copy.
x < Y True if the elements of x are lexicographically less than those of y.
x > Y True if the elements of x are lexicographically greater than those
ofy.
x <= Y True if the elements of x are lexicographically less than or equal to
those ofy.
Container Operations 187
Expression Explanation
x >= Y True if the elements of x are lexicographically greater than or
equal to those of y.
x.swap(y) Swaps all the elements of x with those of y.
ExpreSSion Explanation
c: : reverse- iterator A reverse iterator for the container.
C: :const- reverse - iterator A constant reverse iterator for the container.
x.rbegin() Returns a reverse iterator that references the last ele-
ment in the sequence.
x. rend() Returns a past-the-end reverse iterator for the begin-
ning of the sequence.
Expression Explanation
c x(n, elem) Constructs a new sequence that is initialized to
contain n copies of the element elem.
C x(iterl, iter2) Constructs a new sequence whose elements are
copies of those referenced by the iterators from
i terl up to i ter2 in an existing container.
x.insert(iter, elem) Inserts a copy of elem directly before the ele-
ment referenced by iter.
x.insert(iter, n, elem) Inserts n copies of elem directly before the
element referenced by iter.
x.insert(iter, first, last) Inserts copies of the elements referenced by the
iterators first up to last immediately be-
fore the element referenced by iter.
x.erase(iter) Deletes the element referenced by iter from
the sequence.
x.erase(first, last) Deletes all the elements in the range of the it-
erators first up to last.
x. clear() Deletes all elements from the container.
containers provide the additional operations, shown in Table 7.3, for conven-
ience in dealing with sequences.
In addition to the requirements of all sequence containers, the operations
shown in Table 7.4 can be provided. Whether these operations are provided for
a given container is determined by efficiency. They are provided only for con-
tainers for which they can be implemented to run in constant time.
The push_back () and push_front () methods allow easy appending
of new elements to either end of the sequence. pop_front () and
pop_back () provide the same convenience for deleting elements from either
end, and front () and back () allow direct access to the first and last ele-
ments without using iterators. Finally, a subscript operation can be defined to
provide true random access to any element in the container.
Vectors 189
7.3 Vectors
The vector is probably the simplest of the sequence containers. It is imple-
mented as a contiguous block of memory similar to an array. The features of a
vector include the following:
• amortized constant time insertions and deletions at the end,
• linear time insertions and deletions at interior positions,
• automatic storage management, and
• satisfies all the requirements of a reversible container and a sequence.
The easiest way to think of a vector is as an array that can grow if it is not
large enough. This is done for you via the automatic storage management fea-
ture of the vector. When a vector is created, a finite amount of storage is allo-
cated to hold the elements. If elements are inserted and exceed this storage
190 7: Sequence Containers
space, a larger block of memory is allocated, the old elements are copied to the
newly allocated memory, and the old memory is freed.
This operation is convenient, but it is computationally very expensive. It
also means that the constant and linear time operations are only average speeds.
If an operation requires new memory to be allocated, it will take much longer.
This performance difference makes the vector unsuitable for applications where
additional memory will have to be allocated frequently. The average operation
speeds are valid only if additional memory allocation is done infrequently, as
was intended by the designers.
The vector makes efficient use of memory, since very little information other
than the actual data in the vector is stored. This is in contrast to a container such
as the list, discussed later in this chapter, which requires two extra pointers to be
associated with every element in the list. The list has the advantage that it sup-
ports constant time insertions and deletions at any point in the list. Unfortu-
nately, this extra speed can be attained only by consuming extra memory. One
way to look at this is that you can either store information that will reduce the
calculations you will have to perform, consuming space, or you can recalculate
something every time, consuming time. This is called the space-time tradeoff,
and program designers are faced with it constantly.
As we examine the containers, we will continually face the space-time trade-
off, and it will become one of the principal contributing factors in the decision to
use one container in preference to another. Since it is expensive for a vector to
obtain additional space, you should create it with enough space for its applica-
tion. In the unlikely event that additional space is required, you can obtain it at
the cost of decreased performance. This is preferable to having the program fail
due to insufficient memory. On the other hand, if too much storage is preallo-
cated and not used, memory is being wasted. Therefore, the vector is best suited
to those applications where an accurate estimate of the storage requirements can
be obtained before the application begins to execute. Otherwise, you should
consider another container.
Let us begin our study of how to use the vector by examining Listing 7.1-a
simple program to sum the values stored in a vector of integers.
Vectors 191
#include <vector>
#include <algorithm>
#include <iostream>
void maine)
{
vector<int> vect(lO,O);
vector<int>: :iterator iter;
int i, sum = 0;
ostream_iterator<int> outStream(cout, " H);
for(i=0;i<10;i++) vect[i] = i;
copy(vect.begin(), vect.end(), outStream);
cout « endl;
2 3 4 567 8 9
#include <vector>
#include <algorithm>
#include <iostream>
vect.erase(vect.begin(»;
copy(vect.begin(), vect.end(), outStream);
cout « endl;
cout « "size = " « vect.size() « endl
cout « "max size = " « vect.max_size() « endl;
Vectors 193
o 0 0 0 0 0 0 0 0 0 0 0 000
size - 15
max size = 1073741823
capacity .., 15
o 0 0 0 0 0 0 0 0 0 0 0 0 0 0 98
size = 16
max size = 1073741823
capacity = 30
o 0 0 0 0 0 0 0 0 0 0 0 0 0 98
size ... 15
max size 1073741823
capacity '"' 30
The vector is created with fifteen elements, as is shown by the printout of the
vector, and confirmed by the value returned from size ( ). The value of
max_s i ze () is the total number of elements that can be stored in the vector
and remains unchanged despite insertions and deletions. The value returned by
capaci ty () is the amount of memory currently allocated for the vector ex-
pressed as the number of elements it can contain. push_back ( ) increases the
length of the vector by one, and erase () decreases it by one. When
push_back () requires extra storage, the amount of memory allocated to the
vector is doubled so that it will have space to grow without having to constantly
perform memory allocations.
In some cases, you will be able to predict the amount of space required by
the vector when it is created, yet wants to be able to use a vector of a variable
length less than this maximum size. The method reserve () is provided to
deal with this problem. reserve () accepts a single parameter that is the
maximum number of elements for which memory should be allocated. If this is
less than the amount of memory currently allocated, no action is taken. If it is
greater than the currently allocated memory, then new memory is allocated and
the values from the old memory copied. This reallocation invalidates any itera-
tors, references, or pointers to the vector, since they would now point to the deal-
located storage. This same problem occurs when storage is reallocated by
194 7: Sequence Containers
methods such as push_back () and you must be very careful to avoid using
such pointers or iterators after they have been invalidated. Fortunately, the itera-
tors returned by methods such as begin ( ) and end ( ) are updated when stor-
age is reallocated and they can be used safely. This is shown in the Listing 7.3.
#include <vector>
#include <algorithm>
#include <iostream>
void main ( )
{
vector<int> vect;
ostream_iterator<int> outstream(cout, " H);
vect.reserve(50);
cout « "size = " « vect.size() « endl;
cout « "capacity " « vect.capacity() « endl;
vect.push_back(5);
vect.push_back(34);
copy(vect.begin(), vect.end(), outstream);
cout « endl « "size = " « vect.size() « endl;
cout « "capacity = " « vect.capacity() « endl;
size = 0
capacity 0
size = 0
capacity "" 50
5 34
size = 2
capacity 50
#include <vector.>
#include <algorithm>
#include <iostream>
using names pace stdi
void maine)
{
vector<int> vect ( lO , 0) i
vector<int>: :iterator iteri
int i, sorted = Oi
ostream_iterator<int> outstream(cout, " ")i
while ( ! sorted) {
196 7: Sequence Containers
sorted = 1;
for(iter=vect.begin(); iter != (vect.end() - 1);
iter++) {
if(*iter> *(iter + 1» {
iter_swap(iter, iter+l);
sorted = 0;
I:~ 2 -3 4 -5 6 -7 8 -9 10
-7 -5 -3 -1 2 4 6 8 10
Since the bubble sort algorithm requires that each element be compared with
the one adjacent to it, * iter must be compared to * ( iter + 1). This only
works because the iterator provided by the vector is a random access iterator that
permits this operation.
#include <vector>
#include <algorithm>
#include <iostream>
main ( )
{
vector<bool> by;
vector<bool>: :iterator iter;
bv.push_back(true);
bv.push_back(true);
bv.push_back(true);
bv.push_back(false);
bv.push_back(true);
copy(bv.begin(), bv.end(),
ostream_iterator<bool> (cout, 1111));
cout « endl;
iter = bv.begin()
bv.insert(iter, false);
copy(bv.begin(), bv.end(),
ostream_iterator<bool> (cout, 1111));
cout « endl;
bv.erase(iter);
copy(bv.begin(), bv.end(),
ostream_iterator<bool> (cout, 1111));
cout « endl;
return(O);
[
11101
011101 1
~1_1_1_0_1~______________________________________
Although the latest C++ standard calls for the implementation of the type bool,
few of the previous generations of compilers actually provided it. As a result,
you could not declare a container as vector<bool>. The STL designers rec-
ognized this problem and implemented a special class called the bi t_vector
to address the shortcomings in older compilers. It provides a set of operations
identical to the vector and can be used interchangeably with a vector. Listing 7.6
198 7: Sequence Containers
illustrates how to create and use a bit_vector. This code was compiled with
the HP implementation of the STL.
#include <bvector.h>
#include <algo.h>
main ()
{
bit_vector by;
bit_vector: :iterator iter;
bv.push_back(l);
bv.push_back(l);
bv.push_back(l);
bv.push_back(O);
bv.push_back(l);
copy(bv.begin(), bv.end(),
ostream_iterator<bool>(cout, 1111»;
cout « "\n II ;
iter = bv.begin()
bv.insert(iter, 0);
copy(bv.begin(), bv.end(),
ostream_iterator<bool>(cout, 1111»;
cout « '\n';
copy(bv.begin(), bv.end(),
ostream_iterator<bool> (cout, 1111»;
cout « "\n II ;
return(O);
[11101
011101
11101
1
Listing 7.6 - Using a bit_vector [biCvect.cpp]
Lists 199
7.4 Lists
The vector has several limitations on the speed of insertions and deletions since
they can be performed in constant time only if they are at the end of the vector.
The list overcomes these limitations and provides constant time insertions and
deletions at any point in the list. This is accomplished by using noncontiguous
storage to hold the elements that comprise the list.
As is usual in the world of data structures, every gain in efficiency must be
traded off against a cost in storage or a loss in other capabilities. Although the
list provides fast insertions and deletions, it consumes slightly more storage and
no longer provides fast random access to the elements. However, bidirectional
iterators are provided that can traverse the list in either the forward or reverse di-
rections even though this must be done in a sequential fashion.
A list consists of a series of nodes, each of which contains an instance of
some other data type. (See Figure 7.2) Each node augments the data type stored
within it by adding a pointer to the next node in the list and the previous node.
This arrangement allows the list to be traversed in either the forward or reverse
direction, although this must be done one node at a time in a linear fashion. This
linear arrangement makes the list suitable for applications that need to traverse
the data sequentially rather than accessing elements in random order.
The forward and reverse pointers must be added to every node stored in the
list, and this can consume a considerable amount of storage in a large list. On
many machines, a pointer occupies 4 bytes, resulting in an overhead of 8 bytes
per node to store data in a list. If the size of the data stored in the list nodes is
large, say 100 bytes or more, the storage overhead required by the pointers will
be relatively small. On the other hand, should you create a list of 4-byte inte-
gers, the storage required by the pointers is greater than the size of the data!
This additional space required by the pointers must be considered before decid-
ing to adopt the list as a container.
You might be wondering how insertion and deletion of list nodes can be per-
formed in constant time when the list can be traversed only in linear time. The
answer is that it is all done by the manipulation of pointers. Consider a list with
a node to be deleted. The node to be deleted is pointed to by the nodes directly
before and after it and can be reached only by following one of those pointers.
For ease of discussion we'll call the node to be deleted B, the node before it A,
and the node following it C. Node B can be deleted by making the next pointer
of A point to C and the previous pointer of C point to A. This is illustrated in
Figure 7.3, where the original pointers to node B are shown as dashed lines and
the pointers after its deletion as solid lines.
200 7: Sequence Containers
"
.) .,)
Since the node being deleted will no longer be used, and no program will fol-
low the pointers in the node, it is not necessary to change its pointers. The entire
deletion process was accomplished by changing only two pointers, and the time
required for this is constant regardless of the position of the node being deleted.
Insertion is likewise accomplished via pointer manipulation. In this case, we
assume that we are trying to insert a new node B between two existing nodes A
and C. The next pointer of A must be made to point to the new node, as must
the previous pointer of C. The process is completed by having the previ-
0us pointer of B point to A and the next pointer of B point to C. This is illus-
trated in Figure 7.4, where the original pointers are shown as dashed lines.
Once again, this operation can be accomplished in constant time since only
four pointers need to be manipulated. The operation is the same regardless of
the position of the insertion point in the list.
The storage for a list is managed very differently from that of a vector.
Whereas the storage for the vector is allocated as a single, contiguous block, the
storage for the list is allocated in discrete chunks. This means that adding and
deleting nodes to and from a list will never require the storage reallocation per-
formed by a vector.
Another difference between the vector and the list is that pointers and refer-
ences to the nodes in a list are not invalidated by insertion and deletion. In the
vector, insertion and deletion require the physical movement of data to accom-
modate the new element or to occupy the space vacated by a deleted element.
As a result, references to memory locations are no longer valid since different
data occupy the memory. In the list, only the pointers in the nodes are changed,
while the node itself continues to occupy the same memory location. Pointers or
Lists 201
...
--
next next ~,. next
previous ... previous previous
/
A B
c
references to a deleted node should not be used, since the memory might well
have been reused by your own or another application.
The HP implementation of the STL list uses its own memory management
scheme where a block of memory is preallocated and managed directly by the
list. This was done because implementations of the operators new and delete
are notoriously inefficient. Each time they are invoked, they call the operating
system or the runtime support provided by the C++ compiler. These use a com-
plex, general-purpose memory management scheme that must deal with variable-
sized chunks of memory being allocated and freed. It is possible to write a
A ~[ next~ c
-rr~ous1
#include <string>
#include <iterator>
void maine)
{
list<string> nameList;
list<string>: :iterator iter;
string *temp;
int i;
ostream_iterator<string> outStream(cout, "\n");
char* sourceList[9] = {
"Sam" , "Mary" , "Sue" ,
"Allen" , "Chris" , "Delores",
"Melissa" , "Patricia" , "Donald"} ;
for(i=O;i<9;i++) {
temp = new string(sourceList[i]);
for(iter=nameList.begin(); iter 1=
nameList.end(); iter++) {
if(*temp < *iter) break;
}
nameList.insert(iter, *temp);
delete temp;
}
Lists 203
Allen
Chris
Delores
Donald
Mary
Melissa
Patricia
Sam
Sue
The names are stored in a list whose template parameter is the class
string. A for loop is used to insert each of the names into the list. Each
time through the loop, a linear search is performed to find the position at which
the name should be inserted. In some cases, such as the initial insertion, the in-
sertion point is after the last element in the list. In this case, the search sets the
iterator indicating the insertion point to be the past-the-end iterator for the list.
The insertion is then performed by inserting before the past-the-end iterator so
that the inserted element becomes the last in the list. The actual insertion is done
by the method insert, which requires an iterator indicating the element to in-
sert before and the element to be inserted. Note that the element that was in-
serted is deleted immediately after its insertion and that this has no effect on the
value stored in the list. This is because the insert method copies the value of
the element into storage allocated by the list itself.
This example demonstrates how to maintain a list in sorted order by hand. In
practice, it would make more sense to use the function lower_bound ( ) to de-
termine the insertion position, as it would use a binary search rather than a linear
search.
Since the list is designed to provide fast insertions and deletions from the middle
of the list, it provides several operations to aid in performing such manipula-
tions. These operations can be applied only to list containers and no others. In
204 7: Sequence Containers
addition to the list-specific operations, some of the general STL algorithms are
duplicated as methods of the list class. Although the general STL algorithms can
be applied to a list, the class-specific methods usually offer an advantage, such
as increased performance, and should be used in preference to the more general
algorithm.
The list class defines several versions of the s p lice method, which destruc-
tively moves elements from one list and inserts them into a second list. The term
destructive move means that the element is removed from the first list after being
inserted into the second. All of the splice ( ) methods operate in linear time.
The three signatures are:
void splice(iterator position, list<T>& x);
The method unique () works the same as the STL function of the same
name. It removes duplicate adjacent values from a sorted list in linear time:
void unique ( ) ;
The method merge ( ) performs a destructive merge of two sorted lists. The
elements from the argument list are merged into the target list, and the argument
list is empty when the method terminates. If two equal elements are merged, the
element from the target list will precede the element from the argument list. The
method requires linear time:
void merge(list<T>& x);
The reverse () method is similar to the STL function of the same name
and reverses the order of the entire list in linear time:
void reverse();
The sort () method sorts the entire list and is stable, since it preserves the
relative order of equal elements. This method requires approximately
O(n log n) time:
void sort();
A more extensive example of the application of lists involves keeping track
of the inventory at a wine store. The store stocks numerous wines that are identi-
fied by the name of the wine, the winery that produced it, the country of origin,
the color, year of production, and price. Queries are allowed that permit various
attributes to be specified, and all wines matching those attributes will be re-
trieved. The format of a query is a series of attribute name and value pairs. In
the case of the year, one of the operators (=, ! =, <, >, <=, >=) must be
used between the attribute name and its value to specify the desired relationship.
The query to find all red French wines bottled before 1994 would look like this:
color red country france year < 1994
The query is evaluated by AND'ing all the red wines with all the French
wines, then AND'ing this with the wines bottled before 1994. Although it is
possible to make a linear pass through the data to identify all the wines that have
these properties, it is more efficient to build inverted indices. An inverted index
is a list of all wines that have the same value for a given attribute. An inverted
index is constructed for each value of every attribute of interest so that when all
the red wines are needed there is a list of them readily available.
As an example of inverted lists, consider a list of people indexed by sex and
hair color. Lists are maintained for every value of the attributes sex and color.
206 7: Sequence Containers
These lists contain the identifiers of the people whose attributes have these val-
ues. These inverted lists are shown in Figure 7.5.
To find all the males with black hair, you have to find the IDs that are on
both the male inverted list and the black hair inverted list. If the two lists are
maintained in sorted order, then the balance line algorithm can be used to find
the elements common to the two lists in linear time. The balance line algorithm
initializes two iterators, one referring to the start of each list. If the two elements
are equal, one of them is output to the result list, and both iterators are advanced.
When one element is less than the other, the iterator for the lesser element is ad-
vanced, but no value is copied to the output list. This continues until the end of
one of the lists is reached, when the algorithm terminates.
This implementation stores the wine inventory information in an ASCII file
that is read into memory and inverted every time the program is started. A typi-
cal inventory file appears in Listing 7.8.
Lists 207
Listing 7.8 contains the wine name, winery, country, grape variety, year of
production, color, and price. Internally, the wines are stored as instances of the
class Wine in a list. The class Wine as well as classes to represent queries and
tokens produced during the scanning of queries are defined in the header file
wine. h, shown in Listing 7.9.
#ifndef WINE_H
#define WINE_H
#include <string.h>
#include <iostream>
#include <list>
enum Fruit {
unknownl,
apple,
blueberry,
burgundy,
pinotNoir,
mosel,
zinfadel
};
enum Color {
unknown2,
white,
rose,
red
} ;
208 7: Sequence Containers
enum Country
unknown3,
france,
germany,
spain,
italy,
canada,
usa,
chile,
safrica,
australia
};
///////////////////////////////
// the representation of a wine
///////////////////////////////
class Wine {
public:
Wine() ;
friend bool operator==(const Wine& wI,
const Wine& w2);
friend bool operator==(const Wine& w,
const int& i);
friend bool operator«const Wine& wI,
const Wine& w2);
friend ostream& operator«(ostream& os,
const Wine& w);
friend istream& operator»(istream& is,
Wine& w) ;
/////////////////////////////////////////////////////
// a list that has a textual or numeric
// identifier associated with it
/////////////////////////////////////////////////////
class IdList {
public:
Lists 209
IdList ( ) ;
void sort ( ) ;
list<int> contents;
};
int id;
};
/////////////////////////////////////////////////////
/ / the representation of a token as returned
/ / by the scanner
/////////////////////////////////////////////////////
class Token {
public:
Token() ;
enum TokenType {
overflow, / / token too long for buffer
ident, / / a string value
nameW, // keyword "name"
countryW, // keyword "country"
wineryW, // keyword "winery"
yearW, // keyword "year"
colorW, // keyword "color"
210 7: Sequence Containers
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
II the representation of a query
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
class Query {
public:
Query ( );
void Clear();
char wineName[NAME_LEN);
char wineryName[NAME_LEN);
Color wineColor;
Country countryName;
Fruit fruitName;
int year;
Token: : TokenType yearOper;
} ;
IIIIIIIIIIIIIIIIIII
II global functions
IIIIIIIIIIIIIIIIIII
ostream& CountryPrint(ostream&, Country);
ostream& FruitPrint(ostream& as, Fruit);
ostream& ColorPrint(ostream& as, Color);
Country CountryRead(istream&);
Fruit FruitRead(istream&);
Color ColorRead(istream&);
IIIIIIIIIIIIIIIIIII
II global variables
IIIIIIIIIIIIIIIIIII
extern int wineSeqNum;
Lists 211
#endif
Although there are an unlimited number of wine names, wineries, and years
of production, there are a finite number of grape varieties, countries, and wine
colors. As a result, enumerations are defined for fruit type, country, and color.
The definition of the class Wine provides data members to store all the informa-
tion from the inventory file as well as the methods necessary to handle the data.
Since there can be an unlimited number of wine names, wineries, and years
of production, these are inverted as lists of inverted lists. The classes Named-
List and IntList are used to represent an inverted list that has a textual or
integral identifier associated with it. For example, an instance of a NamedList
would be used to store references to all of the wines that have that particular
name. The actual name is stored in the class NamedList, and the references to
all wines with that name are stored in the list contained within the class. The in-
stances of the class NamedList are themselves stored in a list that can then be
searched to find the references to a particular wine.
The Token class is used to return tokens from the query scanner to the query
parser. The Query class stores the query specifications after they have been
parsed. The query parsing process is not involved in the list processing of this
example and is not examined in detail. The scanner, Get Token ( ), breaks the
query into a series of tokens representing keywords, identifiers, and operators in
the query. The token is then passed to the query parser, ParseQuery ( ),
which checks to see that the tokens conform to the definition of a query and con-
structs an instance of the class Query to represent the textual query.
The implementation of the classes and functions declared in wine. h is in
the file wine. cpp.
#include <wine.h>
Wine: : Wine ( )
{
name[O] winery[O] I \0 'i
country france;
variety burgundy;
year = 0;
color white;
price = 0.0;
212 7: Sequence Containers
sequenceNumber 0;
break;
case spain:
as « "Spain";
break;
case italy:
as « "Italy";
break;
case canada:
as « "Canada";
break;
case usa:
as « "USA";
break;
case chile:
as « "Chile";
break;
case safrica:
as « "SAfrica";
break;
case australia:
as « "Australia";
break;
default:
as « "unknown";
break;
}
return(os);
}
is » input;
Lists 215
strlwr(input) ;
if(O == strcmp(input, "apple"» return(apple);
if(O == strcmp(input, "blueberry"»
return(blueberry);
if(O == strcmp(input, "burgundy"»
return(burgundy);
if(O == strcmp(input, "pinotnoir"»
return(pinotNoir);
if(O == strcmp(input, "mosel"» return(mosel);
if(O == strcmp(input, "zinfadel"»
return(zinfadel);
return(unknownl);
is » input;
strlwr( input) ;
if(O strcmp(input, "white"» return(white);
if(O == strcmp(input, "rose"» return(rose);
if(O == strcmp(input, "red"» return(red);
return(unknown2);
is » input;
strlwr(input);
if(O strcmp(input, "france"» return(france);
if(O strcmp(input, "germany"» return(germany);
if(O strcmp(input, spain"» return(spain);
if(O strcmp(input, italy"» return(italy);
if(O strcmp(input, canada"» return(canada);
if(O strcmp(input, usa"» return(usa);
if(O strcmp(input, chile"» return(chile);
if(O strcmp(input, safrica"» return(safrica);
if(O strcmp(input, australia"»
return(australia);
return(unknown3);
IdList: : IdList ( )
216 7: Sequence Containers
{
}
NamedList: :NamedList()
{
id[O] = '\0';
IntList: :IntList()
{
id = -1;
}
IntList::lntList(int i)
{
id = i;
}
Token: : Token ( )
{
type = overflow;
strVal[O] = '\0';
intVa1 = 0;
floatVa1 = 0.0;
Query: : Query ( )
{
Clear() ;
}
void Query::C1ear()
{
wineName[O] = wineryName[O] '\0' ;
wineColor = unknown2;
countryName = unknown3;
fruitName = unknown1;
year = -1;
}
Notice in this file the definition of opera tor== for many of the classes.
This operator is required by the STL algorithms in the functions unique ( ) and
remove ( ). In general, the use of any STL function that performs a search re-
quires that operator== be defined on the class that is being searched. Simi-
larly, functions that copy values will want opera tor= defined, and functions
that sort or compare values will want opera tor<.
Functions such as Co1orRead ( ) and Co1orPrint ( ) are used to convert
enumerations between text and the internal representation and vice versa. These
functions work directly with streams so they can be used either with cin and
cou t or string streams.
Most of the logic for the query system is in the file wines. cpp, shown in
Listing 7.11.
#inc1ude "wine.h"
218 7: Sequence Containers
#include <fstream>
#include <strstream>
#include <cctype>
void ProcessQueries();
list<int>* EvalQuery(Query& qry);
void Makelndices(Wine& w, int);
template<class T>
int EvalOp(T& a, Token: : TokenType op, T& b);
int ParseQuery(char* line, Query& qry);
char* GetToken(char* line, Token& tok);
void ListMerge(list<int>& result, list<int> adder);
class WinePrinter
public:
void operator()~Wine& w){cout «w;}
};
main ( )
(
if(!stockFile) (
cerr « "cannot open stock file\n";
return(l);
/////////////////////////////////////////////////////
/ / Read stock list from file and build
// inverted indices.
/////////////////////////////////////////////////////
stockFile » temp;
while(stockFile) {
Lists 219
iter = stock.insert(stock.end(),temp);
Makelndices(temp, temp.sequenceNumber);
stockSize++;
stockFile » temp;
ProcessQueries();
return(O) ;
}
/////////////////////////////////////////////////////
// Take a single instance of a wine and add
// its iterator in the stock list to all the
// inverted indices to which it belongs.
/////////////////////////////////////////////////////
void Makelndices(Wine& w, int seqNum)
{
list<NamedList>: :iterator namelter;
list<IntList>: :iterator intlter;
char done;
for(namelter=wineName.begin(), done=O;
namelter != wineName.end(); namelter++)
if(*namelter == w.name) {
(*namelter).contents.push_back(seqNum);
done = 1;
break;
}
}
if ( ! done) {
namelter = wineName.insert(wineName.end(),
NamedList(strlwr(w.name»);
(*namelter).contents.push_back(seqNum);
}
for(namelter=wineryName.begin(), done=O;
namelter != wineryName.end(); namelter++)
if(*namelter == w.winery) {
(*namelter). contents .push_back.(seqNum);
done = 1;
break;
}
if ( ! done) {
namelter = wineryName.insert(wineryName.end(),
NamedList(strlwr(w.winery»);
(*namelter).contents.push_back(seqNum);
220 7: Sequence Containers
for(intlter=wineYear.begin(), done=O;
intlter != wineYear.end(); intlter++)
if(*intlter == w.year) {
(*intlter).contents.push_back(seqNum);
done = 1;
break;
}
i f (!done)
intlter = wineYear.insert(wineYear.end(),
IntList(w.year»;
(*intlter).contents.push_back(seqNum);
wineColor[w.color] .push_back(seqNum);
wineVariety[w.variety] .push_back(seqNum);
wineCountry[w.country] .push_back(seqNum);
///////////////////////////////////////////
// Repeatedly read query lines, parse them,
// evaluate the query, and print the result.
///////////////////////////////////////////
void ProcessQueries()
{
char queryLine[256];
Query query;
int posn;
list<int>* result;
list<Wine>: :iterator winelter;
list<int>: :iterator intlter;
ostream_iterator<int> outstream(cout, "\n");
while(cin.getline(queryLine, 256» {
if(!(posn = parseQuery(queryLine, query») {
result = EvalQuery(query);
winelter = stock.begin();
intlter = result->begin();
while(intlter != result->end(»
if(*winelter == *intlter) {
cout « *winelter « '\n';
intlter++;
winelter++;
Lists 221
delete result;
/////////////////////////////////////////////////////
// Evaluate a query by merging all the inverted lists
// that contain elements that match the query.
/////////////////////////////////////////////////////
list<int>* EvalQuery(Query& qry)
{
list<int>* result;
list<int> tempResult;
char first = 1, found;
list<NamedList>: :iterator namelter;
list<IntList>: :iterator intIter;
ostream_iterator<int> outStream(cout, " H);
found = 0;
if(qry.wineName[O] != '\0') {
for(nameIter=wineName.begin(); nameIter !=
wineName.end() && (! found); nameIter++)
if(*nameIter == qry.wineName) {
if(first) {
first = 0;
result->insert(result->end(),
(*namelter).contents.begin(),
(*nameIter).contents.end(»;
}
else
ListMerge(*result,
(*namelter).contents);
found = 1;
found = 0;
if (qry.wineryName [0] != '\0') {
for(nameIter=wineryName.begin(); nameIter !=
wineryName.end() && (! found); nameIter++)
if(*nameIter == qry.wineryName) {
222 7: Sequence Containers
if (first) {
first = Oi
result->insert(result->end(),
(*namelter).contents.begin(),
(*namelter).contents.end(»i
else
ListMerge(*result,
(*namelter).contents)i
found = 1i
}
}
if(qry.fruitName 1= unknown1) {
if (first) {
first = Oi
result->insert(result->end(),
wineVariety[qry.fruitName] .begin(),
wineVariety[qry.fruitName] .end(»i
else
ListMerge(*result,
wineVariety[qry.fruitName])i
if(qry.wineColor 1= unknown2) {
if(first) {
first = Oi
result->insert(result->end(),
wineColor[qry.wineColor].begin(),
wineColor[qry.wineColor].end(»i
}
else
ListMerge(*result, wineColor[qry.wineColor])i
}
if(qry.countryName 1= unknown3) {
if (first) {
first = Oi
result->insert(result->end(),
wineCountry [qry. countryName] .begin(),
wineCountry [qry. countryName] .end(»i
}
else
ListMerge(*result,
wineCountry[qry.countryName])i
Lists 223
if(qry.year > 0) {
for(intlter=wineYear.begin()iintlter 1=
wineYear.end()i intlter++) {
if(EvalOp((*intlter).id, qry.yearOper,
qry.year» {
tempResult.insert(tempResult.end(),
(*intlter).contents.begin(),
(*intlter).contents.end(»;
tempResult.sort()i
}
}
i f (first) {
first = 0;
result->insert(result->end(),
tempResult.begin(), tempResult.end(»i
}
else
ListMerge(*result, tempResult);
}
return(result)i
}
/////////////////////////////////////////////////////
// Use a modified balance line algorithm to merge
// the adder list into the result list so that only
// the common elements remain in the result.
/////////////////////////////////////////////////////
void ListMerge(list<int>& result, list<int> adder)
{
list<int>: :iterator reslter, addlter, tempi
ostream_iterator<int> outStream(cout," ")i
reslter = result.begin()i
addlter = adder.begin();
else {
addlter++;
reslter++;
}
result.erase(reslter, result.end(»;
}
/////////////////////////////////////////////////////
// Evaluate the result of a boolean operator
// on two operands.
/////////////////////////////////////////////////////
template<class T>
int EvalOp(T& a, Token: : TokenType op, T& b)
{
switch(op) {
case Token: : eq :
return ( a == b );
cout « "EvalOp: " « a « "=" « b « '\n';
break;
case Token:: gt :
return ( a > b );
cout « "EvalOp: " « a « ">" « b « '\n';
break;
case Token: :It:
return ( a < b );
cout « "EvalOp: " « a « "<" « b « '\n';
break;
case Token: :geq:
return ( a >= b );
cout « "EvalOp: " « a « ">=" « b « '\n';
break;
case Token: :leq:
return ( a <= b );
cout « "EvalOp: " « a « "<=" « b « '\n';
break;
case Token: :neq:
return ( a != b );
cout « "EvalOp: " « a « "!=" « b « '\n';
break;
}
return(O);
}
Lists 225
/////////////////////////////////////////////////////
// Parse a textual query and transform it into
// an instance of the Query class. If an error
// occurs, it will return the position in the
// line at which the error was found;
// otherwise it will return o.
/////////////////////////////////////////////////////
int ParseQuery(char* line, Query& qry)
{
Token tok;
char* lp;
istrstream* bufstream;
lp = line;
while(lp = GetToken(lp, tok»
switch(tok.type) {
case Token: :nameW:
lp = GetToken(lp, tok);
if(tok.type != Token: :ident)
return(lp - line);
strcpy(qry.wineName, tok.strVal);
break;
case Token: :wineryW:
lp = GetToken(lp, tok);
if(tok.type != Token: :ident)
return(lp - line);
strcpy(qry.wineryName, tok.strVal);
break;
case Token: :colorW:
lp = GetToken(lp, tok);
if(tok.type != Token: :ident)
return(lp - line);
bufstream = new istrstream(
tok.strVal, NAME_LEN);
qry.wineColor = ColorRead(*bufstream);
delete bufstream;
break;
case Token: :countryW:
lp = GetToken(lp, tok);
if(tok.type != Token: :ident)
return(lp - line);
bufstream = new istrstream(
tok.strVal, NAME_LEN);
qry.countryName = CountryRead(*bufstream);
delete bufstream;
break;
case Token: :fruitW:
226 7: Sequence Containers
lp = GetToken(lp, tok);
if(tok.type != Token: :ident)
return(lp - line);
bufstream = new istrstream(
tok.strVal, NAME_LEN);
qry.fruitName = FruitRead(*bufstream);
delete bufstream;
break;
case Token: :yearW:
if(!(lp = GetToken(lp, tok»)
return(lp - line);
if(tok.type != Token: :eq &&
tok.type != Token: :leq &&
tok.type != Token: :geq &&
tok.type != Token: :It &&
tok.type != Token: :gt)
return(lp - line);
qry.yearOper = tok.type;
if(!(lp = GetToken(lp, tok»)
return(lp - line);
if(tok.type != Token: :intval)
return(lp - line);
qry.year = tok.intVal;
break;
}
return(O);
/////////////////////////////////////////////////////
// Scan the input line to find and return the
/ / next token. On error, the token type is
// Token: :overflow. The function returns a
/ / pointer to where scanning stopped or NULL
// when there are no more tokens on the line.
/////////////////////////////////////////////////////
char* GetToken(char* line, Token& tok)
{
char buf[32], *pt, done = 0, identified = 0;
pt = buf;
while( (*line , ') II (*line == '\t'» line++;
switch(*line)
case '=':
Lists 227
line++;
if(identified) return(line};
if(isdigit(buf[O]» {
i f (strchr (buf, '.'» {
tok.floatVal = atof(buf);
tok.type = Token: :floatval;
228 7: Sequence Containers
else {
tok.intVal = atoi(buf);
tok.type = Token: :intval;
}
return (line) ;
}
if(O == strcmp(buf, "country"» {
tok.type = Token: :countryW;
return(line) ;
}
if(O == strcmp(buf, "color"»
tok.type = Token: :colorW;
return (line) ;
}
if(O == strcmp(buf, "name"» {
tok.type = Token: :nameW;
return(line);
}
if(O == strcmp(buf, "winery"» {
tok.type = Token::wineryW;
return (line) ;
}
if(O == strcmp(buf, "year"»
tok.type = Token::yearW;
return ( 1 ine) ;
}
if(strlen(buf) > NAME_LEN) {
tok.type = Token: : overflow;
return ( 1 ine) ;
}
tok.type = Token: :ident;
strcpy(tok.strVal, buf);
return ( 1 ine) ;
Looking at wines. cpp, we see that several lists are declared at the start of
the file. These are the inverted lists that will hold the numeric identifiers associ-
ated with instances of Wine that have particular properties. Some of these, such
as wineColor, are used to index a finite number of values. Such inverted lists
are stored as an array of lists of integers. Other attributes, such as wineName,
can have an unpredictable number of values and are stored as a list of Named-
List or IntList so that each inverted list has an identifier associated with it.
Lists 229
The function main () is fairly simple. It reads the inventory data from the
file, calls Makelndices ( ) to build the inverted lists, and then starts to process
the queries.
The first list processing is encountered in the function Makelndices ( ). It
is passed the newly read wine information and the instance identifier. It then
adds the identifier to each of the inverted lists to which it pertains. You might
wonder why an integral identifier is used rather than the iterator for the instance.
The reason for this is that the merge algorithm to be used in query evaluation re-
quires that the lists be sorted and that the bidirectional iterator supported by the
list does not define opera tor< and, hence, cannot be sorted. The method
push_back () is used to append the identifier to the ends of the lists. Since
these identifiers are assigned in ascending order, the resulting inverted lists will
also be in ascending order.
The function ProcessQuery () reads in the query line and calls
EvalQuery () to have the query parsed and evaluated. The result is returned
as a list of wine identifiers and is processed against the main inventory list, print-
ing out all wine instances that match the identifiers on the result list.
The function EvalQuery ( ) accepts a query and creates a list of wine iden-
tifiers that satisfy the query. This is done by examining each field in the query
and, if it has a non-null value, inserting the appropriate inverted list into the re-
sult list. The first inverted list inserted into the empty result is simply appended
to the end, and subsequent inverted lists are merged using the function List-
Merge () that uses a modified balance line algorithm to produce a result that is
the and of the two lists. The output of this merge function is always a sorted
list. In the case of a query involving a year, special processing must be per-
formed. Since year query operators such as < or> might cause it to match sev-
eral inverted lists, the result is accumulated in a temporary list by appending
each list onto the end. The temporary list is then sorted before being merged
with the rest of the result list.
You might wonder why the insert () method is always used to append
one list onto the end of the other. Although it might seem more natural to use
copy ( ) , this will not work since copy ( ) assumes that the space to receive the
data has already been allocated. In the case of a list this is not so, and the
copy () function overwrites the end of the list, resulting in data loss or a pro-
gram crash. You might consider splice ( ) as an alternative, but it has the un-
desirable property that it deletes the elements from the source list. This destroys
the inverted list so that it cannot be used in subsequent queries. The combining
of several inverted year lists would best be accomplished by merging the two
lists so that a sorted list would result. Unfortunately, the merge () method
230 7: Sequence Containers
destroys the source list. Thus, the new list is appended to the end of the tempo-
rary result, which is sorted before being merged with the result.
The function ListMerge ( ) merges two lists so that the result is the and of
the two lists. The result of the merge is placed in the list represented by the first
parameter. It compares the two lists on an element-by-element basis, and if it
finds an element in the first list that is not in the second, it deletes it. Elements
on the second list that are not on the first are also removed from the result. Only
elements common to both lists remain in the result.
The remaining functions are concerned with low-level query processing and
evaluation, and you can examine the details to determine how they function.
The SGI implementation of the STL also provides the class s 1 i s t, which is
a singly linked list. This class is not in the C++ standard and is only available in
the SGI implementation. It has only forward pointers between nodes, resulting
in less storage overhead. This implies that slist can provide only a forward
iterator rather than the bidirectional iterator provided by the list class.
slist can be more space-efficient for applications that require only a forward
iterator and will never be ported to environments in which the SGI implementa-
tion is not available.
7.5 Deques
The deque is a sequential container similar to the vector. It provides a random
access iterator and is subscriptable in constant time. The difference is that the
vector provides constant time insertions and deletions only at the end, and the
deque provides constant time insertions and deletions at either the front or the
end of the sequence. Insertions and deletions at other positions in the deque re-
quire linear time.
The properties of a deque make it well suited to applications that require fre-
quent additions and deletions at either end of the sequence. A common use of
such a data structure is in the modelling of queues. A queue is a sequence in
which additions are performed at one end and deletions at the other. It works the
same as a queue of customers waiting for a bank teller. Customers leave the
front of the queue to go to the first available teller, and new customers entering
the bank join the end of the queue.
Although the list can offer the same functionality in the same time, it extracts
a price in additional memory usage that the deque does not require. Therefore,
applications that require insertions and deletions at either end with infrequent
Deques 231
insertions and deletions in the middle should use a deque in preference to a vec-
tor or a list.
Insertions and deletions at internal positions invalidate iterators, pointers, and
references to the deque, and these should not be used after an insertion or dele-
tion at an internal position.
The capability of a deque that permits insertions and deletions at either end in
constant time requires a special implementation that can support these opera-
tions. The vector has to grow only at one end, so a block of contiguous memory
is allocated and new values are added to empty space at the end of the memory
block. In the deque, the same basic strategy is used except that rather than in-
serting the first value at the beginning of the memory block, it is inserted in the
middle of the block. This leaves empty space before and after the elements of
the deque so that it can grow in both directions, as shown in Figure 7.6.
This solution does not handle the problem of what happens when the block of
memory becomes full. The vector handles this problem by allocating a larger
block and then copying the data from the old block to the new one. The deque
takes a different approach and allocates a new block without deleting the original
one. New elements are then stored in the additional memory block. This creates
the problem of locating the block that contains a particular element. This is
solved by providing a storage map that points to all the blocks in the deque.
When the first element of the deque is inserted, only memory block 0 is allo-
cated, and the new element is inserted in the middle of this block. As more ele-
ments are added to the end of the deque, the first block becomes full and a
second block is allocated. The map contains pointers to each block and, like the
blocks themselves, is filled from the middle so that blocks can be added to either
end. This process can continue until the map becomes full, when anew, larger
map is allocated and the pointers from the old map are copied to the middle of
the new map. The old map is then deleted.
One other difference between the vector and the deque is that as more ele-
ments are added, they can both grow, but only the deque can shrink when ele-
ments are deleted. When enough elements are deleted from a deque so that one
of the blocks becomes empty, the empty block is deleted and the map updated to
reflect the new arrangement of blocks. As a result, the deque can make more ef-
ficient use of memory than the vector. Of course, the difference will only be ap-
parent in applications that delete a lot of elements.
232 7: Sequence Containers
elem3
Map elem2
elem 1
Block 0
elemO
block 0
block 1
Block 1
elem4
In the case of the deque, the calculation is complicated by the fact that the
deque can be spread over several physical memory blocks. The first step is to
determine the block in which the element resides and then the offset of the ele-
ment within that block. This is then added to the starting address of the block to
yield the final memory location of the element. This calculation would look like
this:
offsetFromStart = indexOf(deque[O]) + n
blockNumber = offsetFromStart / elementsPerBlock
offsetlnBlock = offsetFromStart - (blockNumber *
elementsPerBlock)
addr(deque[n]) = addr(blockNumber) + offsetlnBlock *
bytesPerElement.
Deques 233
As you can see, this calculation is far more complicated than that used by the
vector. The additional computations take time, rendering subscripting of a de-
que slower than subscripting of a vector. This is summarized in the following:
insertions to drop further if the insertion point is farther from one of the ends.
The list provides the same insertion time regardless of position.
These factors make vectors suited for applications where all insertions occur
at the end, whereas deques can be used for applications requiring insertions at ei-
ther end, and lists can be used for applications that make insertions at any
position.
In terms of space, the deque requires the additional space for the map of the
allocated blocks. This is a small fraction of the space occupied by the data and
can usually be ignored. The list, on the other hand, maintains forward and re-
verse pointers for each node in the list. If the data stored in the nodes are small,
the size of the pointers can actually be larger than the data. In any case, these
pointers occupy a significant amount of memory and must be taken into account.
To illustrate the process involved in deciding on a particular container, we
will consider the problem of calculating the marks for a group of students. The
input to the program consists of a series of lines of data, each containing a stu-
dent's name followed by a list of integer marks. These data are presented to the
program in unsorted order, and the program must calculate the average mark for
each student and print out the names and averages sorted alphabetically.
Let us consider the requirements for a container to store the sequence of stu-
dents. There is no way to determine the exact number of students, although an
approximate upper bound can be found. The requirement that the output be
sorted means that the results cannot simply be calculated on the fly and printed,
but must be stored so that they can be sorted. Since the printing process makes a
single linear pass through the data, only sequential access is required. The best
way to sort the data is to use the STL sort () function, which sorts by
Choosing a Sequence Container 235
7.7 Strings
The STL provides a string class implemented as a reversible sequence container.
This container has been generalized so that it can not only contain characters, but
it can contain any type that is similar to a character and treat it the same way as a
C character string is treated. While the utility of treating other types as strings
might not be immediately obvious, there are indeed situations that require other
types to be treated in a manner similar to strings. The main reason for the gener-
alization is to allow the class to be used with different representations of charac-
ters such as ASCII, Unicode, etc.
The string class provides numerous functions for manipUlating strings. Since
the string class is a sequence container, it provides all the operations you would
expect of a sequence container. These include the iterators and the familiar be-
g in () and end ( ) functions for retrieving iterators to the beginning and end of
the string. The class also provides all of the familiar string-handling operations
including assignment, concatenation, insertion, deletion, substring, comparison,
and string length.
Most of the class methods are overloaded so that they are interoperable with
arrays of the type stored in the container. This makes it convenient to use strings
Strings 237
#include <string>
#include <iostream>
#include <string.h>
void main ( )
{
string sl = "The";
string s2 = "dog";
char cstr1[16];
const char* output;
char space[2];
strcpy(cstr1, "brown");
strcpy (space, " ");
sl += space;
sl += s2;
sl.insert(4, string(cstr1) + space);
output = sl.c_str();
Method Explanation
at(posn) Returns a reference to the member at posn.
c_str( ) Returns a pointer to a zero-terminated array of
the string contents.
length( ) Returns the number of members in the string.
append(str) Appends str to the string.
assign(str) Assigns str to the string.
insert(posn, str) Inserts str before posn in the string.
erase(posn, len) Removes len members starting at posn.
replace(posn, len, Replaces the len members starting at posn
str) with str.
find(str) Returns the location of the first occurrence of
str.
find_first_of(str) Returns the location of the first member of the
string that is in str.
compare(str) Lexicographically compares the string to str.
Returns a negative value if the string is less
than s tr, zero if they are equal, and a positive
value if the string is greater than s tr.
operator+=(str) Appends str to the string.
operator=(str) Assigns str to the string.
operator== Returns true if string equals str.
(string, str)
operator< (string, Returns true if string is less than str.
str)
Listing 7.12 demonstrates how ordinary character arrays can be used in con-
junction with the basic_string class. Two instances of string are de-
clared and assigned values that are zero-terminated character arrays. string is
a specialization of basic_string to store type char and is the common sub-
stitute for a character array. operator= for the class basic_string is
overloaded so that zero-terminated arrays can be assigned directly. The zero-
terminated arrays cstr1 and space are assigned the values "brown" and " ",
respectively. The string sl has space and s2 concatenated onto it, showing
that operator+= is defined as concatenation and that it is overloaded to work
with both strings and arrays.
The next line inserts a string into the string sl just before position 4. An it-
erator could have been used to indicate the insertion position instead of a nu-
meric position. The string that is inserted is built from two zero-terminated
arrays. The first array is converted to a string using a constructor. The second
array is appended to the new string using an overloaded version of opera tor+,
which appends the array onto the string and returns the result as a string.
The final assignment uses the method c_str () to retrieve a pointer to a
zero-terminated array from the instance of the string class. This array is then
printed.
Listing 7.13 demonstrates the use of some ofthe string-searching methods.
#include <string>
#include <iostream>
void maine)
{
string sl = "the name of the game";
string s2 = "the";
string vowels = "aeiouAEIOU";
string: : size_type posn;
string: : size_type startPos;
240 7: Sequence Containers
posn = s1.find(s2);
if(posn string: :npos)
{
cout « "s1 does not contain \"" « s2 « "\""
« endl;
else
{
cout « "the first \"" « s2 «
"\" is in position " « posn « endl;
startPos = 5;
posn = s1.find(s2, startPos);
if(posn string: :npos)
{
cout « "s1 does not contain \"" « s2 «
"\" after posn " « startPos « endl;
else
{
cout « "the first \"" « s2 «
"\" after position " « startPos «
" is in position " « posn « end1;
posn = sl.rfind(s2);
if(posn string: :npos)
{
cout « "s1 does not contain \"" « s2 «
"\"" « endl;
else
{
cout « "the last \"" « s2 «
"\" is in position " « posn « endl;
startPos = 10;
posn = sl.rfind(s2, startPos);
if(posn == string: :npos)
Strings 241
else
{
cout « "the last \"" « s2 «
"\" before position " « startPos «
" is in position" « posn « endl;
posn = sl.find_first_of(vowels);
if(posn string: :npos)
{
cout « "sl contains no chars in vowels" «
endl;
else
{
cout « "the first char in the sequence \"" «
vowels « "\" is in position " «
posn « endl;
posn = sl.find_first_not_of(s2);
if(posn string: :npos)
{
cout « "sl only contains chars in s2" « endl;
else
{
cout « "the first char not in the sequence \""
« s2 « "\" is in position " « posn «
endl;
242 7: Sequence Containers
The program in Listing 7.13 begins by using the method find () to locate
the first occurrence of "the" in the string "the name of the game". This either re-
turns the position of the occurrence, if there is one, or returns string: :
npos, if there is no such occurrence. string: : npos is a special value that is
guaranteed not to be a valid position in the string and is used to indicate that the
string being sought was not found.
The next call to find ( ) shows that the search need not begin at the start of
the string but can start at some position later in the string. This position must be
specified numerically since an iterator cannot be used for this purpose.
The method rfind() performs the same operation as find() but starts
searching from the end of the string rather than the beginning. The second call
to rfind ( ) shows that the search can being before the end of the string. In this
case, the offset is measured from the end of the string rather than the beginning.
Listings 7.14 and 7.15 use the methods find_first_of () and find_-
f irst_not_of ( ) to find the first element in the string that occurs in the pa-
rameter string or is absent from the parameter string, respectively.
#include <string>
#include <iostream>
void maine)
Strings 243
string s1 "abc";
string s2 "def" ;
string s3 "abc";
string t "true" ;
string f = "false";
81 a abc
82 '" def
s3 .. abc
sl.compare(s2) - -1
sl.compare(s3) - 0
81 < 82 ... true
81 <- 82 ... true
81 > 82 ... false
81 >- 82 ... false
81 82 - false
81 1- 92 - true
Listing 7.14 shows the methods and operators for comparing strings. The
method compare () lexicographically compares one string to another and re-
turns a negative in t if the first string is less than the second, zero if they are
equal, and a positive int if the first is greater. All of the comparison operators
are defined on strings and perform as you would expect. Both the compare ( )
method and the operators are overloaded to work with arrays as well as instances
ofbasic_string.
#include <string>
#include <iostream>
void main()
{
int i1 [] = {I, 2, 4, 8, 0 1 i
int i2[] = {16, 32, 64, 0 li
int look_for[] = {8, 16, Oli
const int* int-ptri
basic_string<int>: :iterator iteri
basic_string<int> sl = i I i
basic_string<int>: : size_type posni
cout « "original = "i
for(iter = sl.begin()i iter 1= sl.end()i iter++)
{
cout « *iter « " "i
1
cout « endli
cout «"sl.length() " « sl.length() « endli
sl.append(i2)i
cout « "after append = "i
for(iter = sl.begin()i iter 1= sl.end()i iter++)
{
cout « *iter « " "i
cout « endl;
posn = sl.find(look_for)i
if(posn basic_string<int>: :npos)
{
cout « "substring not found" « endli
Strings 245
else
{
cout « "substring in position " « posn «
endl;
int_ptr = sl.c_str();
cout « "final array ".
I
while(*int_ptr)
{
cout « *int_ptr++ « " ";
cout « endl;
original - 1 2 4 8
s1.length() - 4
after append - 1 2 4 8 16 32 64
substring in position 3
final array - 1 2 4 8 16 32 64
string sl("alpha");
string s2(sl);
string: :iterator iter = sl.begin();
246 7: Sequence Containers
*iter = I X ';
cout « sl « endl;
cout « s2 « endl;
IXIPha
alpha
Thus, when one of the strings is altered, the other remains unchanged, as it
should. This must be true whether a reference-counted implementation is used
or not.
The use of reference counts can also have implications for multithreaded ap-
plications since several instances of a string might share data. In such cases, you
will have to implement locking on the string primitives to ensure mutual
exclusion.
• copying
• finding a character in an array of characters
• determining the number of characters in a zero terminated array
• converting characters to an integer representation and
• converting integers to a character representation
All of the methods and functions that work with strings use the methods of
char_trai ts to manipulate the underlying representation of a character. Any
methods or functions you write to work with strings should use the char-
_trai ts methods as well.
8
Associative Containers
8.1 Introduction
Although the sequence containers can certainly hold any data you wish to store,
they are limited in how the data can be retrieved. There are two ways to find a
value stored in a sequential container-subscripting and searching. If the con-
tainer is subscriptable and you remember the position where a particular piece of
data was stored, you can use the subscript to efficiently retrieve the data. If the
container is not subscriptable or you do not remember the position in which it
was stored, the only option is to perform a search. This could be a binary search
for sorted subscriptable containers or a linear search for other containers.
The associative containers store data so that they can be retrieved using an
identifier known as a key. The key is a group of one or more attributes of an ob-
ject that can be used to identify it. For example, a student might be identified by
a student number, which is unique to that student. Each member of the container
is associated with a key when it is placed in the container. The member can be
retrieved at a later time by simply specifying the key.
Some associative containers allow for the use of duplicate keys. This would
be useful if you wanted to store your student information in an associative con-
tainer and use the surname as the key. This means that several objects might be
returned in answer to a query for a single key.
The associative containers maintain an association between a key and a mem-
ber of the container similar to the way our minds associate a name with a face.
When we are given someone's name, it is a simple matter to recall his or her
face. The indexing techniques used by the mind are vastly different from those
of a computer; however, the resulting functionality is the same.
How the data are stored by an associative container should not concern the
user of such a class. An associative container guarantees that the data can be
stored by key and retrieved. It says nothing about the physical order in which
the data elements will be stored. If you need to process the data in a specific or-
der using an iterator, then one of the sequence containers might be more appro-
priate. Applications that use associative containers usually store data and
retrieve them in random order, without worrying about the order in which they
are physically stored. While these statements are true of associative containers
in general, the STL associative containers are implemented so that iterators can
return the elements in sorted order.
The STL requires that iterators for associative containers return the elements
in non-descending order, where the order is determined by comparing the key as-
sociated with each element. This implies that iterators for associative containers
which do not contain duplicates will return the elements in ascending order.
The STL provides four types of associative containers. Two of these store
only the keys themselves without any data being associated with the key. The
other two store data that are associated with keys. Each of these categories is
further divided into a class that stores only unique keys and one that allows du-
plicate keys. These classes are shown in Table 8.1.
The sets store only the keys-not any values associated with the keys. This
makes sets suitable for applications where the key itself is the datum and you are
primarily concerned with determining if a particular key is in the container. In
this way. the STL set is similar to the mathematical concept of a set and can be
considered a generalization of it. Although some computer languages provide a
set type, they are usually limited in their cardinality (the number of unique keys
that can be stored in the set) and the types that can be stored in the set. The STL
set is not limited in these ways and can store any number of values of any type.
The map stores data that have a key associated with them. The keys are not
limited to just integers or enumeration types but can be any type. Maps imple-
ment a storage model similar to the associative memory of our own minds, mak-
ing them suitable for retrieval of data that can be identified by a key. It is
well-suited to the implementation of associative arrays-arrays whose subscripts
need not be simple integers or enumerations.
algorithms. The associative containers support all of the operations that can be
performed on a container and Table 8.2 shows only the requirements specific to
associative containers.
In addition to these common operations, there are two different versions of
the insert () method for containers that support unique keys and duplicate
keys.
Most of these operations, such as insert () and erase ( ), will be famil-
iar to you from working with the sequence containers. Others, such as find ( )
and count ( ), are specific to the associative containers, although they do have
analogues in the general STL algorithms that can be used on sequence contain-
ers. The insert () and erase ( ) operations are similar to the include ( )
and exclude ( ) set operations provided by some programming languages that
support sets.
Note that some of the operations will perform differently if used on a con-
tainer that supports duplicate keys than if used on one that supports unique keys.
count ( ) is an example of one such operation. Although it will perform as ex-
pected on containers with either key type, the results are limited to 0 or 1 for
containers that support unique keys.
The insert () operation is more interesting, since the return type is en-
tirely different when the container supports duplicate keys than when unique
keys are supported. When duplicate keys are allowed, the familiar iterator refer-
ring to the newly inserted element is returned. Containers supporting unique
keys actually return two results in the form ofa pair<iterator, hool>.
252 8: Associative Containers
Expression Explanation
C: : key_type The type of the key for the container.
C: : key_compare The type of the key comparison function that defaults
to less<key_type>.
C: : value_compare The same as key_compare for sets and multisets.
For maps and multimaps, it is an ordering function on
the first member of a pair, where the first member is
the key.
CO Creates an empty container using Compare ( ) , a tem-
plate parameter, as a comparison function.
C(cmp) Creates an empty container using cmp as a compari-
son function.
C(iterl, iter2, cmp) Creates a container and inserts the elements from
i terl up to i ter2 using the comparison function
cmp.
C(iterl, iter2) Creates a container and inserts the elements from
i terl up to i ter2 using the comparison function
Compare ( ).
C: : key_comp () Ceturns the key comparison object.
C: :value_comp() Returns the value comparison object constructed from
the key comparison object.
x.insert(val) For containers with unique keys, inserts val provided
it is not already in the container. It returns a
pair<iter, bool> where the second member in-
dicates if the insertion was successful and the first re-
fers to the inserted value. For containers supporting
duplicate keys, it returns an iterator referencing the in-
serted value.
x.insert(iter, val) Behaves the same as x. insert (val) but uses
iter as a hint as to where to insert the value.
x.insert(iterl, iter2) Inserts the values from iterl up to iter2.
x.erase(key) Deletes all members whose key is key. It returns the
number of elements deleted.
x.erase(iter) Deletes the element referenced by iter. No result is
returned.
Associative Container Operations 253
Expression Explanation
x.erase(iterl, iter2) Deletes all elements in the range from i terl up to
i ter2. No result is returned.
x.clear() Deletes all elements in the container. No result is
returned.
x.find(key) Returns an iterator referencing an element with a
key of key or end ( ) if there is no such element.
x.count(key) Returns the number of elements whose key equals
key.
x.lower_bound(key) Returns an iterator referencing the first element
whose key is not less than key.
x.upper_bound(key) Returns an iterator referencing the first element
whose key is greater than key.
x.equal_range(key) Returns a pair or iterators delimiting a range of ele-
ments with the key, key.
Since only unique keys are supported, it is possible that an insertion might
fail due to an attempt to insert a value that is already in the container. The suc-
cess of the insertion is conveyed to the invoking program as the Boolean value in
the second member of the pair. The first member of the pair is always an
iterator referring to the element being inserted in the container, whether this was
the first time it was inserted or not.
Unlike some of the sequence containers, insertion and deletion to and from
an associative container do not invalidate iterators, pointers, or references to
members of the container not directly involved in the operation. In this respect,
the associative containers behave just like the list for exactly the same reason.
The memory for each element in the container is allocated as a discrete unit that
is not subject to relocation when other elements in the container are rearranged.
comparison object is used both for ordering the elements within the container
and determining the equality of elements. As such, it must behave the same as
operator< would on the elements. In most cases, less<type> is a good
choice, and that is the default value for the parameter. Similarly, the allocator
defaults to the default allocator so that, often, only a single template parameter is
needed. (See Listing 8.1.)
#include <set>
#include <iostream>
void main ( )
{
set<int> setl;
set<int>: :iterator iter;
int i;
set1.insert(3);
set1.insert(6);
set1.insert(8);
3 is in the set
6 is in the set
8 is in the set
for the data type. This can then be used by the definition of less, shown in the
following:
template <class T>
struct less : binary_function<T, T, bool> {
bool operator() (const T& x, const T& y) const
{
return x < Yi
}i
8.3 Sets
The set is an ordered associative container that supports unique keys. The mul-
tiset, discussed in the next section, is the same as the set but allows duplicate
keys. The set stores only keys rather than a key and data associated with it, as do
the map and multimap. This, combined with the fact that it is sorted, makes the
set perfect for implementing the mathematical concept of a set. Listing 8.2 dem-
onstrates the use of sets.
The stipulation that a set can only store keys is not as restrictive as it seems at
first. A key is some sort of value that identifies a record so that it can be differ-
entiated from other records. There is no requirement that the key be the entire
record, however. It is common to create a class Employee to store the infor-
mation on the employees in a company:
class Employee {
public:
Employee(int id, const char* nam, int ag,
float pa) i
friend bool operator«const Employee& el,
const Employee& e2)i
friend ostream& operator«(ostream& os,
Employee& e)i
private:
int employeeldi
char name[32] i
int agei
int paYi
} i
256 8: Associative Containers
In a case like this, the actual key that identifies each employee is employ-
eeld. This member of the class is only a portion of the class, yet it meets the
requirements of a key in that it uniquely identifies a particular employee. In-
stances of this class can be stored in a set, but it is not immediately obvious how
we store just the key. In truth, the entire instance is stored in the set, but the
comparison function object that determines the equality of two Employee in-
stances is written so that it only compares the employeeld member. One way
to do this is to write opera tor< for the class so that it compares the employ-
eeld members:
bool operator«const Employee& el,
const Employee& e2)
This operator can then be transformed into a comparison function object us-
ing less<Employee>. Since this is the only function used by the set to com-
pare elements, we are in effect storing and retrieving keys, as that is all that is
ever compared. In reality, the keys carry some extra baggage with them, but this
is invisible to the comparison function used for insertion and deletion.
#include <set>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
void main()
{
set<string> supperveggies;
set<string> lunchVeggies;
set<string> allVeggies;
supperveggies.insert(string("broccoli"»;
supperVeggies.insert(string("corn"»;
supperVeggies.insert(string("cauliflower"»;
supperVeggies.insert(string("kale"»;
supperVeggies.insert(string("lettuce"»;
supperVeggies.insert(string("potato"»;
supperVeggies.insert(string("squash"»;
supperVeggies.insert(string("turnip"»;
lunchVeggies.insert(string("avocado"»;
Sets 257
lunchVeggies.insert(string("arugula"»;
lunchVeggies.insert(string("beet"»;
lunchVeggies . insert(string("corn"»;
lunchVeggies.insert(string("capsicum"»;
lunchveggies.insert(string("aubergine"»;
« endl;
set_intersection(supperVeggies.begin(),
supperveggies.end(), lunchVeggies.begin(),
lunchVeggies.end(),
ostream_iterator<string> (cout, "»; II
set_union(supperVeggies.begin(),
supperVeggies.end(), lunchVeggies.begin(),
lunchVeggies.end(),
inserter<set<string> >
(aIIVeggies,aIIVeggies.begin(»);
copy(aIIVeggies.begin(), allveggies.end(),
that beg in () should return an iterator rather than a constant iterator. Check
your implementation for the exact definition of beg in ( ) .
Listing 8.3 shows how sets can be used with user-defined classes.
#include <algorithm>
#include <set>
#include <stdarg.h>
#include <string>
#include <vector>
#include <iostream>
using namespace std;
enum HobbyType {
II zero reserved for var arg usage
unknownHobby = 1,
knitting,
sewing,
reading,
stampCollecting,
coinCollecting,
cardCollecting,
birdWatching,
photography
} ;
class HobbyInfo {
public:
HobbyInfo();
HobbyInfo(HObbyType tyP);
friend bool operator«const HobbyInfo& 1,
const HobbyInfo& r);
friend ostream& operator«(ostream& os,
const HobbyInfo& h);
HobbyType hobbyName;
};
return(os);
Hobbylnfo: :Hobbylnfo()
{
hobbyName = unknownHobby;
class Person {
public:
Person();
Person(const char* nam, int ag, ... );
string name;
int age;
set<Hobbylnfo > hobbies;
friend bool operator«const Person& 1,
const Person& r);
friend bool operator==(const Person& 1,
const Person& r);
};
Hobbylnfo hobbYi
int tmpi
name = nami
age = agi
va_start(ap, ag)i
while«tmp = va_arg(ap, int» 1= 0) {
hobbies.insert(Hobbylnfo«HobbyType)tmp»i
maine )
{
vector<Person> peoplei
vector<Person>: :iterator iteri
ostream_iterator<Person> outStream(cout, "\n") i
Listing 8.3 shows how sets can be used to store the hobbies of a group of
people. The class Person stores the information on a person including his or
her name, age, and the hobbies in which he or she has an interest. Since a person
can be interested in each hobby only once, the set is an ideal data structure for
representing a person's hobbies. All the instances of Person in the program
are stored in a vector for easy retrieval.
If we look more closely at the class Person, we see that the set of hobbies
is declared as follows:
set<Hobbylnfo> hobbies;
rather than this more direct declaration:
set<HobbyType> hobbies;
The reason for introducing a seemingly superfluous class Hobbylnfo is
that it is necessary to make the program compile. When a set is destroyed, it
first invokes the destructor for all of the objects contained within it. This works
fine for a user-defined class for which a, possibly default, destructor is provided.
It also works for the built-in types since the STL header files define special
destructor-like functions for them that do nothing. It does not work for a user-
defined enumeration since the STL attempts to call - HobbyType ( ), which
does not exist. Thus, the enumeration is wrapped in a class for which a destruc-
tor can be provided.
The constructor for the class Person uses a variable-length argument list
since it cannot be predicted how many hobbies must be passed. These hobbies
are then added to the set using the insert ( ) method.
262 8: Associative Containers
At times, you will find that you want the set comparison to behave differently
than opera tor<. In this case, you must define your own function object to
perform the comparison. The declaration for a comparison object for the class
HobbyInfo would look like this:
class HobbyCompare {
public:
int operator() (Hobbylnfo& a, Hobbylnfo& b)
{
return(a.hobbyName < b.hobbyName);
}
};
This defines opera tor () to do the real work of the comparison, and the
programmer is free to make it behave differently than operator<. To use this
comparison object, the declaration of the set in the class Person would have
to be changed to the following:
set<Hobbylnfo, HobbyCompare> hobbies;
Another interesting point about this program is the order in which the hob-
bies are printed. Examining the program, we see that the hobbies are printed by
using an iterator to traverse the set and print out all the values stored in the set.
When the data for Jimmy Joe was inserted, photography was placed before coin
collecting, but in the output the order was reversed. This is easily explained by
noting that the associative containers store their contents in sorted order. Refer-
ring to the definition of the enumeration, we see that coin collecting does indeed
come before photography, and hence is printed first. You can always depend on
an iterator for a sorted container to traverse the contents in an order determined
by the comparison object for the container.
If you think this example is somewhat contrived, you are correct. For this
particular application, a sequence container could be used just as well as a set.
The use of a set would make more sense if the application had to answer queries
of the form "list all people with an interest in photography." Even then, a se-
quence container could still be used and, assuming it was sorted, yield compara-
ble efficiency. The set begins to make sense if it is implemented with a hash
table, which would be more efficient in most cases. The advantages and disad-
vantages of hash tables are discussed later in this chapter.
Multisets 263
8.4 Multisets
The multiset is just a set that can contain duplicate values. The programmer will
notice only a small difference in the APls of the set and the multiset. As men-
tioned previously, the insert ( ) method for the multiset returns a single itera-
tor indicating the position of the newly inserted element rather than the pair
returned by the set. Of course, the count () method might return a value
greater than one. This was impossible for the set, which did not allow
duplicates.
When working with multisets, the possibility always exists that there is more
than a single copy of each value in the container. The find () method will re-
turn an iterator referencing the first element in the container that matches the key
provided to find ( ). To find all of the elements that match a certain key, the
method equal_range () should be used. This will return an iterator pair,
indicating the range of elements that match the key. This range will be contigu-
ous, since all of the elements in the container are sorted.
8.5 Maps
The map differs from the set in that it stores a key and an associated value
rather than just a key. Although this might not seem like a major difference, it
makes the map suitable for use in a different range of applications. One of the
main applications of the rna p is in the implementation of associative arrays.
The associative array is similar to a regular array, except that the key can be
of any type, not just an integer. This greatly extends the usefulness of the array.
Consider the case where you are running a charity campaign with several can-
vassers knocking on doors to collect money for a worthy cause. Each day the
canvassers report their names and the amounts of money they collected. It is ex-
pected that each canvasser will make several reports, and new canvassers will
join the campaign and others quit as time goes by. The information from the
canvassers is collected into a file such as Listing 8.4.
Ted 66
Alice 98
Bob 78
Alice 45
Ted 23
Joan 64
Fred 28
264 8: Associative Containers
Joan 96
Alice 21
Bob 36
Alice 32
Ted 83
Joan 55
Fred 70
What is needed is a program like Listing 8.5 to read the information on the
canvassers and generate a report showing the total amount collected by each can-
vasser. To do this, we have to associate an accumulator with the name of each
canvasser. When each line of data is read, the program will look up the can-
vasser's name in the map, retrieve the associated accumulator, and increment it.
#include <map>
#include <fstream>
#include <iostream>
#include <string>
class Total {
public:
int totalValue;
Total(): totalValue(O) {}
Total operator+=(int a)
{
totalValue += a;
return(*this);
} ;
ostream& operator«(ostream& os, const Total& t)
{
os « t.totalValue;
return(os);
main ( )
{
ifstream dataFile("canvas.dat", ios: :in);
char name[16];
int dollars;
Maps 265
i f ( ! dataFile)
cerr « "cannot open data file\n";
exit(l);
for(iter=canvassers.begin(); iter !=
canvassers.end(); iter++) {
cout « (*iter).first « ", " «
(*iter).second« '\n';
return(O);
Alice, 196
Bob, 145
Fred, 98
Joan, 272
Ted, 172
After reading what the program had to do, you are probably surprised that it
is so short. This is due, in large part, to the convenience offered by the map
class. Let us begin by looking at the representation of the accumulator.
The accumulator is represented by the class Total, consisting of a single in-
teger value. Operators are defined to increment the accumulator value and to
print the value onto a stream. This class is used in preference to an integer since
it can initialize itself when it is constructed. Since these accumulators will be
created by the map itself, and not our program, this is an important capability.
The map to store the accumulators is declared as follows:
map<string, Total, less<string> > canvassers;
266 8: Associative Containers
This is read as stating that the map will have a key of type string, a value
of type Total associated with each key, and a comparison function
less<string> that can be used to compare keys. The comparison parameter
could have been left to the default value since that is less<keyType>, which
would be the same thing.
Data are read from the input stream and inserted into the map by this
statement:
canvassers [name] += dollars;
The first thing you notice here is that the map is subscriptable with the key
used as a subscript. The type returned by the subscript operator is a reference to
the instance of the class Tota 1 associated with the key. Once a reference to the
accumulator is found, opera tor+= is used to add the new value onto it. When
the end of the stream is reached, all the calculations have been done and the re-
sults stored.
The next question is, "How do you subscript a value that does not exist?"
The first time the subscript operator is used with a name, the name will not be in
the map and you would expect the operation to fail. The subscript operation
succeeds due to the clever implementation of the subscript operator, shown in
the following:
Allocator<T>: : reference operator[] (const key_type& k)
{
return (*«insert(value_type(
k, T(»». first». second;
}
As you can see, the subscript operator really performs an insertion. If the ob-
ject being inserted is already in the container, it returns a reference to it. If it is
not in the container, it inserts it and then returns a reference to the newly inserted
element. Regardless of whether the key is in the container, it returns a reference
to a valid object of the correct type. The result is a subscript operator that is
much easier for the programmer to use.
The final step is to use an iterator to print all the values onto a stream. Pay
particular attention to how the dereference operator works in conjunction with a
map. The trouble with a map is that there are two pieces of information associ-
ated with each element referenced by an iterator-the data stored there and the
key for the data. The usual way the STL represents two things is with the class
pair. The same technique is used here with the key stored in the first member
of the pair and the data associated with the key stored in the second member of
the pair. Thus, the line
cout « (*iter).first « ", " «
Maps 267
(*iter).second« '\n';
is used to print out both the key and the accumulator associated with each key.
Some applications will want to insert a value into a map using the in-
sert () method directly rather than the subscript operator. As you can see
from the body of the subscript operator shown previously, the insert ( )
method takes a parameter of type value_type. This is a typedef, and the
actual type is pair<const key_type, value_type>. The construction
of such a pair for the preceding subscript operator would look like this:
insert(pair<const key_type, value_type>(k, T(»)
The examples in Chapter II demonstrate the use of the insert () method
for maps.
8.5.1 Multimaps
The multimap is just like the map but supports duplicate keys. This difference
necessitates a change in the insert () method, which returns an iterator refer-
encing the newly inserted member rather than a pa ir as returned by the in-
sert () method for the map. The methods count () and equal_range ( )
can also indicate that more than a single element matches a given key. Other-
wise, the multimap behaves just like the map.
Multimaps are useful in applications where it is normal to have several val-
ues associated with a single key. One such case would be storing information
about people where the surname is used as a key.
8.6.1 Trees
A tree is a nonlinear data structure that can be much faster to search than a se-
quential data structure such as a list. The simplest, most common type of tree is
the binary tree. A binary tree consists of a collection of nodes joined by
branches. Each node has either one or two children and is an internal node, or
has no children and is a leaf. Figure 8.1 shows a typical binary tree and some of
the terminology associated with trees.
The terminology used with binary trees is a combination of terms used for
real trees and the terms used in connection with family trees. In Figure 8.1, B
and C are said to be the children or offspring of A, which is said to be their par-
ent. A is a grandparent of D and E, whereas B, C, D, E, and F are all descen-
dants of A. B and C are siblings since they share a common parent. B is the root
of a subtree consisting of the nodes B, D, and E.
One of the most common uses of the binary tree is as a data structure that can
be searched rapidly. A binary tree can be searched if the nodes are arranged so
that the left child of any node is less than the parent and the right child is greater
than or equal to the parent. A tree that has this property is called a binary search
tree and is depicted in Figure 8.2.
To understand why it is faster to search for a value in a binary search tree
rather than a list, note that at each node, you decide whether the value for which
you are looking is to the left or right of the current node. Each time such a deci-
sion is made, the number of nodes remaining to be searched is cut approximately
in half. This means that the number of nodes that must be compared to find a
value is less than or equal to the number of times the number of nodes in the tree
can be halved. As mentioned before, the number of times a value n can be
"'---root
....--branch
height ..... internal
node
halved is log2n. This is the same as the height of the tree, which is the number
of nodes from the root to the lowermost leaf.
Insertion into a binary search tree is performed by first searching for the
value in the tree. When the leaf level is reached and the value is not found, the
new value is added as the left or right child of the leaf. When a node is deleted,
the greater of its children is promoted to its place. This node now has one extra
child that must be inserted into one of the subtrees of its parent. For example, if
node 8 were deleted from the tree in Figure 8.2, node 14 would move up to take
its place. Node 9 would now be an extra child that must be inserted into the sub-
tree rooted at node 5. In the general case, node 9 might be the root of a subtree,
all of whose values would have to be inserted into the subtree rooted at node 5.
The optimal search performance for a binary tree can be attained only when
the tree is balanced, as shown in Figure 8.3. A binary tree is said to be balanced
if the difference in the heights of any two subtrees with a common parent is less
than or equal to 1. If a tree is unbalanced, then less than half of the tree might be
discarded when a node is examined, resulting in longer search times. In the ex-
treme case, all nodes are left children or right children, and the search becomes a
linear one.
To guarantee optimal search times, binary trees must be kept balanced. This
is done by recursively balancing every subtree in the tree. When an unbalanced
subtree is found, it can be balanced by rotating it about the root.
The tree in Figure 8.4 must be rotated clockwise to balance it. This places
node 5 at the root and makes node 8 its right child. The problem is what to do
with node 7, which used to be the right child of node 5. It is passed to node 8,
the old root, and becomes its new left child. Of course, you can rotate a tree in
the counter-clockwise direction, and all of the foregoing procedures are reversed.
270 8: Associative Containers
In a binary tree, the worst-case search time will be the time to search from
the root to a leaf. If the tree is balanced, the worst-case s~arch time will be ap-
proximately the same for any value at the leaf level. The a~erage performance is
less that the worst case, since the datum might be located above the leaf level. In
a large tree, this can make a significant difference in how long it takes to find in-
dividual values.
For some applications, it is important that the time to retrieve a value be the
same regardless of the value. This can be guaranteed only if all of the nodes are
the same distance from the root. One tree that has this property is a 2-4 tree
[VWY90].
2 9
In a 2-4 tree, every node has between two and four children. The actual data
are stored at the leaf level, and all leaves are the same distance from the root.
The internal nodes contain index information making it possible to locate a value
at the leaf level. Each internal node contains three pieces of information-the
value of the largest descendants of the first, second, and third children. A typical
2-4 tree is shown in Figure 8.5.
Finding a value is as simple as starting at the root and looking at the index in-
formation to determine the subtree that might contain the value being sought.
This process continues down the tree until the leaf level is reached. At that
point, a linear search is performed on the sibling leaves, which are sorted from
left to right. If an internal node has only two children, the third index value is
not used and is represented as a hyphen in the diagram.
Insertion into a 2-4 tree is accomplished by searching for the value from the
root downward. When the leaf level is reached, it is inserted as a new child of
the parent. At this point, several situations can arise. If the parent has two or
three children, the new node is added and everything is fine. If the parent al-
ready has four children, then the new node is added as a temporary fifth child,
and the children must be redistributed to other nodes.
Redistributing the children of a node that has a temporary fifth child is the
most complicated phase of insertion into a 2-4 tree. If the parent has a sibling
that has less than four children, then the children can be shared amongst the sib-
lings at the parent level so that each parent has four or less children. In the case
where the siblings of the parent are all full, there is no choice but to create a new
node at the parent level. When this is done, the five children are split between
the two parent nodes, with one parent receiving two children and the other three.
It is possible that the creation of a new node at the parent level caused its
parent to have a fifth child. When this happens, the same technique is used to re-
distribute the children or to create a new node at the parent level. This series of
changes ripples up the tree and can, in some cases, cause a new root to be cre-
ated. Thus, 2-4 trees are said to grow in height from the root.
Figure 8.6 shows the steps in the insertion of node 18 into the tree from Fig-
ure 8.5.
Here we see that the insertion of node 18 creates a parent with five children.
Since the parent has a sibling with two children, the extra child can be shared
among the siblings at the parent level. Inserting the value of 22 causes no prob-
lems since it can be added as the fourth child of a parent node, as shown in Fig-
ure 8.7.
If we continue this process and insert node 23 into the tree, we find that once
again, we have a parent with a fifth child. This time, all the parent's siblings
have a full complement of children, and there is no choice but to split the parent.
This process is depicted in Figure 8.8.
The deletion of nodes is the opposite of the insertion process. If node 26
were to be deleted from the preceding tree, it would leave the parent with only a
single child. This violates the rule that a node must have between two and four
children, so the situation must be rectified by attempting to share the children
among the parent's siblings. Since the internal node to the left has only three
children, the parent can be deleted and its lone child adopted by the parent with
three children. The resulting tree is shown in Figure 8.9.
The 2-4 tree is not the only member of this family. 2-3 trees and trees of
other sizes are well known. In the general case, an n-m tree has between m and
rml2l children of each internal node. These trees are perfect as indices for infor-
mation stored on disks. The leaf level is stored in a series of physical disk allo-
cation units (sectors or tracks), and the internal nodes are stored in other disk
allocation units and act as an index to the physical allocation units storing the
data.
The actual tree used to implement the STL associative containers is a varia-
tion of the 2-4 tree called the red-black tree. The red-black tree is really a 2-4
tree mapped onto a binary search tree. It differs from a binary search tree in that
the branches connecting the nodes are colored either red or black. Black
branches represent the regular downward branches of the 2-4 tree, and red
branches connect the index values of the internal nodes. Figure 8.10 shows a 2-4
tree and the corresponding red-black tree.
Associative Container Implementation 273
Each node in the 2-4 tree containing n index entries generates n-J nodes in
the red-black tree. Thus, the root of the 2-4 tree creates node 8 at the root of the
274 8: Associative Containers
red-black tree. The two downward branches from the root of the 2-4 tree gener-
ate the downward black branches from the root of the red-black tree. The left
child of the root of the 2-4 tree has three index values and will generate two
nodes in the red-black tree. It creates node 5 in the red-black tree, which is
placed as a right child of node 3 and joined by a red branch to show that it is part
of the index information. The right child of the 2-4 root contains two values and
generates node 9 in the red-black tree. Node 12 is then shown as a regular child
of node 9. The actual data at the root level are not repeated in the red-black tree
if they were already included from the index information.
A red-black tree can be searched just like a binary search tree and can re-
trieve a value in O(log n) time. Insertion into a red-black tree is complex and is
not discussed here.
Associative Container Implementation 275
-redbranch
- - black branch
the same value is simply stored in the linked list rooted there. This is an im-
provement over the previous technique, since only the keys that collide have to
be searched to determine if a key is absent. Still, this is a linear search and
should be avoided if at all possible.
In Figure 8.12, the same values are inserted into the hash table, but no values
are stored in the hash table itself-they are all in a linked list whose head is
stored in the hash table. When a collision occurs during the insertion of node 53,
the new value is simply appended to the linked list. Since there is no particular
order to the elements in this list, new values can be placed at either end of the
list, whichever is more convenient.
The likelihood of a collision can be reduced by increasing the size of the
hash table. The problem with this is that the probability of collision drops to
zero only when the array becomes as large as the number of keys, and every time
we increase the size of the table, more memory is wasted. Once again, we are
faced with a space-time tradeoff.
Since uniform retrieval times cannot be guaranteed for a hash table and the
worst case performance can be considerably worse than a tree, the designers of
the STL decided to use a tree as the underlying data structure for associative
containers. It is possible that a hash table might be better suited to specific ap-
plications but would have undesirable performance characteristics for many
280 8: Associative Containers
ordered and the hash table implementation is not. This causes a change in the
constructor for the associative containers. Whereas the tree implementation re-
quires a comparison function that induces an ordering on the values in the con-
tainer (Le., operator<), the hash table implementation needs a function that
determines if two values are the same (i.e., opera tor==). This also has con-
sequences for the class iterators. Iterators for tree implementations return the
values in sorted order, while the iterators for hash tables return the values in a
seemingly random order determined by the hash function and order of insertion
of the data.
Listing 8.6 uses the bfhash implementation to demonstrate an associative
container's use of a hash table implementation and to give you an idea of the
performance you can expect. This program was compiled by Borland C++ 4.5.
#include <hash.h>
/ / bfhash implementation of associative containers
#include <iostream.h>
#include <dos.h>
#include <set.h>
////////////////////////////////////////////////////
/ / simple minded function to calculate the
/ / difference between two times. This will fail if
// run when the hour changes!!!
////////////////////////////////////////////////////
long TimeDiff(struct time& start, struct time& finish)
{
long s = 0, f = 0;
main ()
{
set<int, less<int> > setO;
hash_set<int, hasher<int>, equal_to<int> >
setl;
int i;
struct time start, finish;
282 8: Associative Containers
gettime(&start);
for(i=O; i< NUM_INSERTS; i++)
setO.find(i);
gettime(&finish);
cout « NUM_INSERTS «
" retrievals from tree = " «
TimeDiff(start, finish) « '\0';
gettime(&start);
for(i=O; i< NUM_INSERTS; i++)
setl. find( i);
gettime(&finish);
cout « NUM_INSERTS «
" retrievals from hash table = " «
TimeDiff(start, finish) « '\0';
cout « "size=" « setl.size() «
", max size=" « setl.max_size()
« ", buckets=" « setl.bucket_couot() « '\n';
return(O);
and is used here. This hash function breaks the key into bytes and then OR's the
bytes together to yield the hash value. If you don't like this, you can provide
your own hash algorithm by declaring a class, as demonstrated in the following:
template <class T>
class MyHasher [
public:
int operator()(T key);
};
The second difference is in the comparison function passed as the third tem-
plate parameter. In the case of a set, less<int> was used to order the data.
Hash tables do not order the data, so we use a function that can determine if two
data items are equal.
Many parameters can be passed to the constructor for the class hash_set
to tune the performance of the hash table. The preceding program has let all
these values default so that all choices will be made by the class itself. The out-
put shows the time for the two methods in hundredths of seconds as measured on
a 90 MHz Intel Pentium. In this case, the hash table is about twice as fast as the
tree.
The number of buckets is the number of entries in the hash table, and we see
that it is just slightly smaller than the number of values stored in the table. This
implies that collisions are relatively rare and, when they do occur, probably only
a list of length two has to be searched. A hash table that is organized this way
will give very near its optimal performance.
The implementation of a hash table using gradual resizing is found in the
subdirectory dmhash and is organized somewhat differently than that of
bfhash. A different header file is provided for each of the containers, and you
must include the header for the container(s) you want. A collection of hash func-
tions is provided in the header hashfun. h, and you can use the one that suits
your needs or define your own.
284 8: Associative Containers
9.1 Introduction
We have all seen adaptors-those little things that go on the ends of hoses so
that they will connect properly with a fitting of a different size. The adaptors
provided by the STL are not much different. They connect onto an existing ob-
ject on one side and make it appear differently on the other side. They do not
really change the object to which they are applied; they simply alter its
appearance.
In Chapter 3, we looked at function adaptors. These adaptors are able to take
a function object as a parameter and transform it into a new function object that
does something different. They do not change the way the function object
passed to them works, but modify the environment in which it is invoked. To the
programmer using the function adaptor, it appears that a new function object has
been created that might have different parameters from the original function
object.
Now we'll look at two more classes of adaptors. One type is used on itera-
tors and is able to alter their behavior. The other is applied to containers and
generates new containers from existing ones.
All of the adaptors share one trait: they change the way something looks and,
in doing so, create something similar to, yet different from, the original. This
technique often saves a lot of implementation effort since it is usually easier to
adapt an existing object to a new purpose than it is to build a new object from
scratch.
This is the simplest way of creating a new container. The restriction is that
the class being created must have a lot in common with the class from which it is
adapted. Although this might seem unlikely, there are in fact some common con-
tainers that are very similar to the containers we have already built.
PUSh~ r
,...-"------'=-----,
pOp
stack
Expression Explanation
push(v) Pushes the value v onto the stack.
pop () Removes the value at the top of the stack without returning it.
top () Returns the value at the top of the stack without removing it.
empty ( ) Returns a Boolean indicating if the stack is empty.
size () Returns the number of values in the stack.
operator«) Compares two stacks.
operator==() Compares two stacks.
Most of these methods are implemented by simply calling the method of the
same name in the underlying container. For example, stack: : push () calls
container: : push_back ( ), stack: : pop ( ) calls container: : pop-
_back ( ), and stack: : operator< () invokes container:: opera-
tor< ( ). This limits the containers on which the adaptor can be based to those
that provide the required methods. It also means that the comparison of two
stacks is defined by the comparison operator of the underlying container.
The following example shows how a stack can be used to convert an expres-
sion from infix to postfix. Infix is the way we usually write arithmetic expres-
sions-for example, a X (b + c). This is called infix because the operators are
placed between the operands. Expressions such as this are evaluated using a se-
ries of rules. The rules are that subexpressions in parentheses should be evalu-
ated before those not in parentheses, mUltiplication and division should be
performed before addition and subtraction, and the expression should be evalu-
ated from left to right unless one of the previous rules dictates otherwise.
Postfix expressions place the operators after the operands to which they ap-
ply. The postfix version of the preceding expression is abc + x. Such an expres-
sion is evaluated by looking from left to right until the first operator is found.
This operator is then applied to the previous two operands and the result replaces
the operator and its operands. This process continues until the end of the string
is reached. In this case, you read from left to right until you encounter the first
operator, +. This is applied to the previous two operands b and c, and the result
of this calculation is used to replace the subexpression bc+. We scan ahead and
find another operator, X, which is applied to a and the result of the previous
calculation.
The Stack 289
One of the things you should note about the postfix expression is that the pa-
rentheses have disappeared. Unlike infix expressions, postfix expressions are
unambiguous and do not need parentheses to clarify the order of evaluation. Us-
ing the rules of evaluation, there is only one way they can be interpreted. There-
fore, the priority of operators and use of parentheses that is required with infix
expressions are eliminated. This lack of ambiguity makes them very useful in
programming languages where complex expressions have to be evaluated. Usu-
ally one of the first steps performed by a compiler is to convert infix expressions
to postfix.
One way to convert an infix expression to postfix is to use a stack and the
following set of rules.
1. Scan the infix expression from left to right.
2. If the input is an operand, copy it to the output string.
3. If it is an operator and it is of higher priority than the operator at the top
of the stack, then push it onto the stack.
4. If it is of lower priority than the top of the stack, then pop the stack and
copy the operators to the output string until an operator of higher
priority is at the top of the stack. Then push the operator that was read.
5. If an open parenthesis is found, then push it.
6. If a closing parenthesis is found, then pop the stack, copying the
operators to the output string, until an opening parenthesis is found.
Then discard both of the parentheses.
We simplify the problem by stating that an operand may be only a single
character, no spaces are permitted in the expression, and the expression is termi-
nated by the special character "$". The use of the special terminating character
makes it easier to recognize the end of the string and simplifies the coding. The
operators have the following priorities, from high to low:
1. *, /
2. +,-
3. $
4. (,)
Listing 9.1 shows a program which uses this algorithm to convert infix ex-
pressions to postfix.
290 9: Adaptors
#include <vector>
#include <stack>
#include <algorithm>
#include <iostream>
int Priority(char c)
{
switch(c) {
case 'I':
case '*':
return(3);
break;
case '+':
case '-':
return(2);
break;
case '$':
return(l);
break;
case '(':
case ')':
return ( -1);
break;
default:
returneD);
break;
}
}
void processChar(char c)
{
if(Priority(c) == D) {
II operand
outString.push_back(c);
return;
}
if(stk.empty() II c == '(') {
II stack is empty or char is
stk . push ( c) ;
return;
}
if(c == ')') {
The Stack 291
main ( )
{
fore; inStream !=
istream_iterator<char, char>(); inStream++)
inString.push_back(*inStream);
for_each(inString.begin(),inString.end(),
ProcessChar);
copy(outString.begin(), outString.end(),
outstream) ;
cout « '\n';
return(O);
292 9: Adaptors
[ a.((b+C)/(d-e)+f)/9$
abc+de-/f+*g/
9.4 Queues
The queue, like the stack, is another form of specialized list, and its operations
are a subset of the general operations supported by sequence containers. A
queue is characterized by having elements placed onto one end of the queue and
removed from the other end. This is exactly the behavior exhibited by a queue
of customers waiting for a teller at a bank. Queues are sometimes referred to as
First In First Out (FIFO) lists. Queues are often used when data have to be saved
and processed later in the same order in which they were placed on the queue.
The operations defined on a queue are summarized in Table 9.2.
These methods are not much different than those provided by the stack, although
it is now possible to access both the value at the front and back of the queue.
The difference lies in the implementation of push () and pop ( ), in that they
now operate on opposite ends of the underlying container using
container: : push_back () and container: : pop_front ( ). As with
the stack, the container on which the queue is based must provide the methods
invoked by the adaptor.
A queue must add values at one end of the container and remove them from
the other. A vector cannot do this in constant time and does not provide all of
the required methods, so it is ruled out as a choice for an underlying container.
As new values are added to the end of a queue and removed from the front, it
tends to creep through memory. This behavior means that it is best implemented
using a container that can handle the queue style of memory usage. Both the
Expression Explanation
push(v) append the value v to the end of the queue
pope ) remove a value from the front of the queue without returning it
front( ) return the value at the front of the queue without removing it
back( ) return the value at the back of the queue without removing it
empty ( ) return a Boolean indicating if the queue is empty
size() return the number of values stored in the queue
operator==() compare two queues
operator«) compare two queues
~.1
push ..... I I I - ~~poP
..
queue
deque and list satisfy these requirements and offer addition and deletion at
either end in constant time. The de que and list can, with some overhead, de-
lete memory once it is no longer used. The deque does not incur the overhead
of the pointers required by the list and is the default container if one is not
specified.
The following shows an example that pushes some values onto a queue and
then pops them off. Users of the HP implementation should note that the tem-
plate parameters as well as the file containing the class have changed in the C++
standard. Also note that the values come out of the queue in the same order in
which they were inserted. Queues, unlike stacks, do not change the order in
which the values are returned. (See Listing 9.2.)
#include <deque>
#include <queue>
#include <iostream>
void maine)
{
queue<int> ql;
int i;
for(i=O;i<10;i++) ql.push(i);
while(! ql.empty(» {
cout « ql.front() « ' ';
ql. pop ( ) ;
cout « endl;
Queues 295
10 1 J 3 4 567 8 9
Expression Explanation
priority_queue(cmp=Compare(» Constructs an empty queue using cmp as a
comparison function.
priority_queue(iterl, iter2, Constructs a queue and initializes it with the
cmp=Compare(» values from i terl up to i ter2, using
cmp for comparison.
push(v) Adds the value v onto the queue.
pop( ) Removes the value at the top of the queue.
top () Returns the value at the top of the queue.
empty ( ) Returns a Boolean indicating if the queue is
empty.
size ( ) Returns the number of values in the queue.
parameters-the type to store in the priority queue, the type of container it will
use to store the values, and a comparison function object that will be used to
compare the values in the container so that they can be sorted. The last two pa-
rameters default to a vector and less, respectively. The comparison func-
tion object should perform the same way as opera tor< ( ). The constructor
allows you to specify an alternate comparison object or to use the default com-
parison object provided as a parameter to the template.
Listing 9.3 shows how values can be inserted into a priority queue in random
order and retrieved in sorted order.
#include <vector>
#include <stack>
#include <iostream>
#include <string>
#include <algorithm>
#include <queue>
#include <functional>
class Person {
public:
string name;
int age;
Person(){age = O;}
person(char* nam, int ag):age(ag)
{name = nam;}
friend bool operator«const Person& pI,
const Person& p2);
friend bool operator==(const Person& pI,
const Person& p2);
};
main ()
{
priority_queue<Person>
pq;
pq.push(PerSon("Fred", 14»;
pq.push(Person("Sally", 7»;
pq.push(person("Jennifer", 9»;
pq.push(PerSOn("Elizabeth", 15»;
pq.push(Person("James", 8»;
while(! pq.empty(» {
cout « (pq.top(».name « " "«
(pq.top(».age « '\n';
pq. pop();
}
return(O);
298 9: Adaptors
Elizabeth, 15
Fred, 14
Jennifer, 9
James, 8
Sally, 7
Notice that you can have the comparison based on one member of the Per-
son class by supplying an appropriate comparison function. In this case, the de-
fault less is used, and operator< is defined appropriately. If you want the
item of least priority at the top of the stack, you could use grea ter<Person>
as a comparison function.
Priority queues call beg in () and end () and, hence, cannot be used with
ordinary arrays. If you really want to use an array as a priority queue, you can
use the underlying heap functions directly. Priority queues do not invoke pop-
_front ( ), so a vector can be used as the underlying container. A list is
ruled out, since the heap is designed to work with contiguous storage that offers
an efficient implementation of opera tor [ ], which heaps use. A deque
could be used, but in the general case, the storage overhead required for this im-
plementation would not be warranted. For most purposes, the vector is the
best choice.
where current is the value of the forward iterator that is used to represent the
position in the container, and self is typedefed to be a reverse iterator.
Reverse iterators also provide constructors that perform the arithmetic neces-
sary to convert a forward iterator into a reverse iterator. The result of a few
arithmetic reversals is an iterator that really does go in the opposite direction of a
forward iterator.
An example of the usage of reverse iterators can be found in Chapter 2,
where iterators in general are discussed.
300 9: Adaptors
#include <vector>
#include <algorithm>
#include <iostream>
main ( )
{
vector<int> vI;
back_insert_iterator<vector<int> > append(vl);
int i;
vl.reserve(50);
for(i=O; i<IO; i++) *append = i;
copy(vl.rbegin(), vl.rend(), append);
copy(vl.begin(), vl.end(),
ostream_iterator<int> (cout, "»; II
Iterator Adaptors 301
returneD);
The constructor for the back_insert_i tera tor requires that the con-
tainer on which it is to operate be passed as a parameter. Once this connection is
made, any assignments to the iterator will be appended to the end of the con-
tainer. This is demonstrated in the for loop, where the values from 1 ... 10 are
appended to the vector. Then the values already in the container are copied in
reverse order to the back insert iterator. The result is the integers from 0 ... 9
followed by the integers from 9 ... O.
One important note is that fifty spaces are reserved in the vector before it is
used. Without this, the vector would grow as each integer was added, resulting
in the reallocation of a larger amount of memory and copying of the values from
the old memory. This would invalidate the iterators in the statement
copy(vl.rbegin(), vl.rend(), append);
since memory would be reallocated during the copy. The result would be that
corrupt values could be placed into the vector or that the program would crash.
This illustrates the true utility of the insert iterators-using them in conjunc-
tion with one of the STL algorithms. The insert iterators are derived from the
class output_i terator and can be used whenever an output iterator is
expected.
When we try to use a front_insert_i terator, we find that the results
are slightly different, as shown in Listing 9.5.
#include <deque>
#include <algorithm>
#include <iostream>
main ()
{
deque<int> dl;
front_insert_iterator<deque<int> > prepend(dl);
302 9: Adaptors
int i;
The program begins by inserting the numbers from 0 ... 9, but after insertion,
they are in the order 9 ... 0. The reason for this is that once the first number, 0, is
inserted, it becomes the front of the deque and the next number is inserted in
front of it. This has the effect of seeming to reverse the order in which the num-
bers are inserted. The copy () function also uses a f ron t -
_insert_i tera tor with the result that the order of the sequence is reversed
again. Thus, the final result has the sequence in both the forward and reverse
orders.
Insert iterators work similarly to back insert iterators in that they insert the
values in the order in which they are assigned to the iterator. The insert iterator
constructor requires both the container into which it should insert and an iterator
indicating the position in front of which it should insert the values. (See Listing
9.6.)
#include <list>
#include <algorithm>
#include <iostream>
main ( )
{
list<int> 11;
list<int>: :iterator iter;
int i;
advance(iter, 5);
copy(iter, 11.end(),
insert_iterator<list<int> >(11, iter»;
copy(ll.begin(), 11.end(),
ostream_iterator<int>(cout, II "»;
return(O);
}
The program begins by inserting the numbers 0 ... 9 into the list and position-
ing an iterator at the position of the 5 in the list. It then copies the values from
the 5 to the end of the list to the position just before the 5. Be careful not to
write an expression like the following:
copy(ll.begin(), 11.end(),
insert_iterator<list<int> >(11, iter»;
This will create an infinite loop since the iterator indicating the beginning of the
range to be copied is before the insertion point. Each time a value is copied, a
value is inserted, with the result that it never reaches the end of the list. This
causes the list to grow to the point where it eventually exhausts the memory of
the computer.
9.7.1 Notl
notl is a function adaptor that takes a function object that returns a Boolean re-
sult and logically negates the result. This is a convenience adaptor that uses the
adaptor unary_negate to actually do the work. It is rarely necessary to call
304 9: Adaptors
unary _nega te directly. The function that is negated must satisfy the require-
ments of a unary_function and return a Boolean result. (See Listing 9.7.)
#include <functional>
#include <iostream>
#include <algorithm>
void main ()
{
int ar[IO] ;
int i;
The program in Listing 9.7 uses not I to negate the result of the function ob-
ject less. The result is that instead of producing a count of the number of val-
ues in the array that are less than 3, it produces a count of the values that are
greater than or equal to 3.
9.7.2 Not2
not2 is a function adaptor that, like notl, negates the result of a function ob-
ject. Rather than negating the result of a unary _function, not2 negates
the result of a binary _function. This is demonstrated in the program in
Listing 9.8.
Function Adaptors 305
#include <functional>
#include <iostream>
#include <algorithm>
void maine)
{
int arl [5], ar2 [5];
bool result[5];
int i;
}
cout « endl;
306 9: Adaptors
arl .. 0 1 2 3 4
ar2 - 4 3 2 1 0
result - 1 1 0 1 1
There are times, such as when providing an argument for an adaptor, when a
function object is required. While you can certainly write your own function ob-
jects by defining operator( ) for a class, you will often have a function that
implements the required operation and wish to tum it into a function object. The
adaptors for pointers to functions and methods can quickly transform a function
or member into a function object.
9.7.4 ptrjun
#include <functional>
#include <algorithm>
#include <iostream>
#include <string.h>
Function Adaptors 307
void maine)
{
char* names[]
{
"John" , "Sue", "Alice", "Bob", "Ted",
"George" , "Taylor", "Gayle", "Cindy", "Rita"
};
char **result, nm[lO];
strcpy(nm, "Taylor");
result = find_if(names,
names + 10,
notl(bind2nd(ptr_fun(strcmp), nm»);
if(result == (names + 10»
{
cout « "not found" « endl;
else
{
cout « *result « endl;
}
ITaylor
Listing 9.9 - Using ptr_fun [ptr_fun.cpp]
The program in Listing 9.9 shows how ptr_fun () can be used to trans-
form a function, strcmp(), into a function object. The result is a pointer-
_to_binary_function that is then adapted using bind2nd() to set the
second parameter to "Taylor". strcmp ( ) returns zero if two strings are equal
and nonzero if they are unequal. This is the opposite of what is needed, so
not1 ( ) is used to negate the result.
The convenience functions mem_fun ( ) and mem_fun1 () do for methods
what ptr_fun () does for functions. The mem_fun () function takes a
pointer to a class method and returns an adaptor that can invoke the method on
an object. The adaptor is an instance of mem_fun_t, which is a subclass of
.!.lllary_function. It is important to understand how the mem_fun_t
308 9: Adaptors
adaptor invokes the method before attempting to use either the adaptor or its
convenience function.
mem_fun ( ) requires a pointer to a method M, which has no parameters and
returns a result of type R. This method pointer is passed to the constructor for
the adaptor mem_fun_t. When an algorithm invokes the method, it does so by
calling opera tor () on the adaptor with the usual syntax for a function call.
operator ( ) of mem_fun_t requires a single parameter-a pointer, P-to
the object on which it should invoke the method. Thus, opera tor () invokes
the function as (P->*M) (). The implication of this is that the parameter an al-
gorithm passes to opera tor () must be a pointer to an object on which the
method can be invoked.
mem_funl () is the same as mem_fun () but works with a method, M,
which takes a single parameter, A, and returns a result, R. mem_funl ( ) returns
an instance of the adaptor class mem_funl_t whose operator ( ) requires
two parameters. The first parameter is a pointer to the object on which the
method should be invoked, and the second is the argument to the method. Thus,
if it is invoked as opera tor ( ) (P , A), it invokes the method as
(P->*M) (A).
#include <functional>
#include <iostream>
#include <algorithm>
using namespace stdi
class Counter
{
private:
int valuei
public:
Counter(){ value = Oi}
int increment(int n)
{
value += ni
return valuei
}
int getValue() {return valuei}
} i
void maine)
Function Adaptors 309
counter *cntr[5] ;
int inc [5] ;
int result[5];
int i;
cout « endl;
}
10 11 12 13 14
There are two important points to notice about this program. First, the array
cntr is an array of Counter*, not an array of Counter. The values in the
array will be passed as the first argument to operator ( ) of mem_fun1_t.
We must make the array object pointers rather than objects because opera-
tor ( ) requires a pointer to an object, not an object. The second point is how
the pointer to the method is generated. The expression Counter::-
increment generates a pointer to the method increment () of the class
Counter.
If you have a container of objects rather than a container of pointers to ob-
jects, you should use the convenience functions mem_fun_ref () and
mem_fun1_ref ( ). These are analogous to mem_fun () and mem_fun1 ( )
but work with references to objects rather than pointers to objects. They return
the adaptors mem_fun_ref_t and mem_fun1_ref_t, respectively. The
program in Listing 9.11 is identical to the program in Listing 9.1 0 except it has
been altered to work with object references rather than object pointers.
310 9: Adaptors
#include <functional>
#include <iostream>
#include <algorithm>
class Counter
{
private:
int value;
public:
Counter(){ value = o;}
int increment(int n)
{
value += n;
return value;
}
int getValue() {return value;}
};
void main()
{
Counter cntr[5] ;
int inc[5] ;
int result[5];
int i;
cout « endl;
Function Adaptors 311
110 11 12 13 14
}
10.1 Introduction
The STL provides facilities for memory management. These include the alloca-
tor class, the raw storage iterator, temporary buffers, the auto_ptr, and some
utility functions. The allocator class is a general-purpose allocator that is the de-
fault allocator used by all the STL containers. The raw storage iterator is an ob-
ject that can write values to uninitialized memory by first constructing the
required object and then assigning a value. auto_ptr is a smart pointer that
can be wrapped around a pointer to dynamically allocated memory so that the
dynamically allocated memory is freed when the scope in which the auto_ptr
is allocated is destroyed.
10.2 Allocators
All STL containers have an associated allocator that is used to perform all
memory-management functions. The Standard c++ Library defines a single
memory model that is used by the containers to perform all dynamic memory
management. An alternate allocator can be provided to implement a different
memory model or allocation strategy.
Implementing an allocator is necessary if you are using a 16-bit memory
model or a larger model not supported by the default allocator. The default allo-
cator uses a general-purpose allocator accessed via operator new. General-
purpose allocators must be able to manage variable-sized blocks of memory.
This causes them to have poor performance relative to an allocator that manages
fixed-sized memory blocks. Thus, you can dramatically improve performance
by writing your own allocator.
#ifndef OBJ_ALLOCATOR H
#define OBJ_ALLOCATOR_H
#include <cctype>
#include <stddef.h>
template <class T>
class ObjAllocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
private:
struct FreeRecord
{
char* begin;
size_t size;
Allocators 315
};
union AlignRecord
long nl;
long double n2;
};
size_t storageSize ;
size_t objSize;
size_t numAllocated;
char* storageArea
size_t nextToAlloc ;
FreeRecord* freeList;
freeListSize;
protected:
int align(size_t n)
{
return (n sizeof(AlignRecord»?
(n) :
(1 + (n / sizeof(AlignRecord») *
sizeof(AlignRecord);
pointer getFreeSpace(size_t n)
{
if(freeListSize == 0)
{
return (pointer)O;
freeListSize--;
return (pointer)freeList[freeListSize] .begin;
public:
ObjAllocator() throw()
{
storageSize = 100;
numAllocated = 0;
objSize = align(sizeof(T»;
storageArea = (char*): : operator new (objSize
316 10: Memory Management
* storageSize);
nextToAlloc = 0;
freeList = new FreeRecord[storageSize);
freeListSize = 0;
};
-ObjAllocator( )
throw ( )
if(storageArea != (char*)O)
{
delete [) storageArea;
}
if(freeList != (FreeRecord*)0)
{
delete [) freeList;
pointer allocatee
size_type n,
const_pointer hint 0)
{
pointer Pi
if( P == 0)
{
return (pointer)Oi
else
{
return Pi
void destroy(pointer p)
{
318 10: Memory Management
p->T: :-T()i
} i
#endif
Listing 10.1 shows an example of what an allocator might look like. Not all
compilers implement all of the capabilities of the latest C++ standard; therefore,
an allocator that works with a particular compiler and implementation might well
be different.
This is a simple-minded allocator that preallocates storage for a limited num-
ber of objects. The constructor begins by computing the size of the storage re-
quired for each object so that each object will be aligned properly. It then
allocates the space.
When space is deallocated, it is added onto a list of free space that is main-
tained as a simple array of FreeRecords. The FreeRecord contains the
address of the start of the free space and an indication of its length. When the al-
located space is exhausted, the allocator examines the most recent entry on the
free list to see if it can satisfy the request. If it can, the storage is removed from
the free list and returned. The simplistic array implementation of a free list does
not lend itself to more complex searches for free space.
The allocation of space is performed by allocatee), which attempts to
get the space from the unallocated area of the storageArea. If there is insuf-
ficient space, it resorts to the free list.
This program is not meant to demonstrate how to manage memory effec-
tively-there are obvious improvements to be made to the allocator. It does
serve to show how a custom allocator might be written. Should an allocator for a
different memory model be required, the typedefs would have to be modified
to reflect the memory model.
Listing 10.2 shows how you can use the allocator.
Allocators 319
#include "objalloc.h"
#include <iostream>
#include <list>
#include <iterator>
class MyClass
{
private:
int n;
public:
MyClass()
{
n = 55;
void main()
{
ObjAllocator<MyClass> alloc;
ObjAllocator<int> intAlloc;
MyClass *p, temp;
p = alloc.allocate(l, (MyClass*)O);
cout « "uninitialized = " « p->getN() « endl;
alloc.construct(p, temp);
cout « "initialized = " « p->getN() « endl;
-
uninitialized - -842150451
initialized'" 55
The raw storage iterator is another output iterator that is distinguished by being
able to write into uninitialized memory. This means that the memory to which it
assigns values does not have to be initialized by the constructor for the class that
will be stored there. The raw storage iterator accomplishes this by invoking the
class constructor on the memory to which it is writing just before performing the
assignment. This initialization is illustrated in Listing 10.3.
#include <iterator.h>
#include <string.h>
#include <malloc.h>
#include <iostream.h>
#include <defalloc.h>
class Person {
public:
Person() i
person(char* nam)i
Person(const Person& p)i
private:
int agei
char name [32] i
friend ostream& operator«(ostream& os, Person& p)i
} i
Person: : Person ( )
{
age = 999i
strcpy(name, "noName")i
cout « "default called\n"i
strcpy(name, nam);
cout « "named called\n";
}
Person::Person(const Person& p)
{
strcpy(name, p.name);
age = 50;
}
main( )
{
char* ar;
ar = (char*)malloc(sizeof(person»;
*raw_storage_iterator<Person*, Person>
«Person*)ar) = Person("George");
cout « *«person*)ar) « "\n";
return(O);
}
#include <memory>
#include <iostream>
#include <vector>
#include <algorithm>
class InitClass
{
private:
int n;
public:
InitClass()
{
n = 99;
cout « "InitClass constructed" « endl;
}
-InitClass(){}
void setN(int nn) { n = nn;}
int getN() {return n;}
friend ostream& operator«(ostream& os,
const InitClass& ic);
};
void maine)
{
vector<InitClass> vectl;
InitClass init;
int i;
const int VECT_SIZE = 3;
ostream_iterator<InitClass> ostr(cout, " ");
vectl.push_back(*(new InitClass()));
}
copy(vectl.begin(), vectl.end(), ostr);
cout « endl;
init.setN(50);
uninitialized_fill(vectl.begin(), vectl.end(),
init) ;
copy(vectl.begin(), vectl.end(), ostr);
cout « endl;
}
An auto_ptr is a smart wrapper around a regular pointer that aids in the par-
tial automation of memory management. When an auto_ptr is created, it is
associated with a pointer to an object that has been dynamically allocated. When
the auto_ptr leaves the scope in which it was created, it is destroyed, and the
destructor of the auto-ptr invokes the destructor of the pointer with which it
is associated. Thus, the programmer no longer has to call delete on dynami-
cally allocated objects, since they can be destroyed at the end of scope by wrap-
ping them in an auto_ptr. Careful placement of auto_ptrs in the correct
scope is a significant step towards the elimination of memory leaks.
The auto_ptr class defines operator* () and operator->() so that
an instance of auto_ptr can be used just as the encapsulated pointer would be
used. Several auto_ptrs can encapsulate the same pointer, but only one of
them owns the pointer at any time. The object referenced by the pointer will be
destroyed when the auto_ptr that owns it is destroyed. Ownership is estab-
lished when an auto_ptr is created and associated with a pointer. When the
value of an auto_ptr is assigned to another auto_ptr or is used as the pa-
rameter to the copy constructor of another auto_ptr, ownership is transferred.
Thus, only the destruction of the auto_ptr that was assigned the value of the
original auto_ptr will trigger the destruction of the object referenced by the
encapsulated pointer. The method release () is provided if the programmer
wants to force an auto_ptr to relinquish its ownership of a pointer. If two
auto_ptrs are constructed to encapsulate the same pointer, the behavior is un-
defined, so this situation must be avoided. (See Listing 10.5.)
#include <memory>
#include <iostream>
class MyClass
{
private:
int n;
public:
MyClass(){n 2;}
-MyClass()
{
cout « "Destroying MyClass" « endl;
}
void setN(int nn) { n = nn;}
int getN() {return n;}
};
void ptrMaker()
{
MyClass *myClassPtr;
{
cout « "entering outer scope" « endl;
myClassPtr = new MyClass();
auto_ptr<MyClass> aptrl(myClassPtr);
aptrl->setN(S);
cout « "n=" « (*aptrl) .getN() « endl;
{
326 10: Memory Management
void maine)
{
ptrMaker ( ) ;
11.1 Introduction
The preceding chapters introduced the concepts of the STL and showed exam-
ples of how you can use it. The purpose of this chapter is to show how you can
use the STL for larger problems. Although this book does not address problems
on an industrial scale, it does describe the solution of many problems encoun-
tered in an industrial setting.
The first example is a banking system that demonstrates the use of several of
the container classes and illustrates how object pointers can be stored in contain-
ers. The second example shows how to build a hierarchical symbol table using
a map of maps. It discusses what is required to create your own container
classes, how to provide iterators for them, and how to make them operate in the
same manner as the containers provided by the STL.
class Account
public:
Account(int ID, float bal = 0.0):
accountID(ID), balance(bal) {}
Account(vector<Customer*> custS);
float GetBalance() {return balance;}
float MakeDeposit(float amt);
float MakeWithdrawl(float amt);
void PrintTransactionList();
virtual void Addlnterest(Date& today) {}
void EraseTransactionList();
void AddTransaction(Transaction* t);
void AddCustomer(Customer& cust);
void DelCustomer(Customer& cust);
void DeleteAccountFromCustomers();
friend ostream& operator«(
ostream& os, Account& a);
friend bool operator«
const Account& a, const Account& b);
friend bool operator==(
const Account& a, const Account& b);
static void setDailyRate(float r)
{dailylnterestRate = r;}
static void setSavingsRate(float r)
{savingslnterestRate = r;}
protected:
A Banking Problem 331
vector<Customer*> customers;
vector<Transaction*> transactionList;
float balance;
static float dailylnterestRate;
static float savingslnterestRate;
int accountID;
};
The interesting point about this class is the way it stores customers and trans-
actions. The customer is a permanent entity in any banking system and we can
assume that we will maintain a container of customers somewhere in the system
that will hold every customer. It is not safe to store the customer objects them-
selves in the account class since deleting an account would delete the customer
who owned it. In many cases, this might not be desirable. Therefore, the ac-
count class maintains the list of customers owning the account as a vector of
pointers to instances of the class Customer, which are actually stored in an-
other data structure. (See Listing 11.2.)
class Custome'r
private:
string firstName;
string lastName;
int customerID;
vector<Account*> accountList;
public:
Customer(string first, string last, int id):
firstName(first), lastName(last) , customerID(id)
{}
Customer(): customerID(O) {}
string GetFirst(){ return firstName;}
string GetLast(){ return lastName; }
int GetCustomerID(){ return customerID;
void AddAccount(Account& act);
void DelAccount(Account& act);
friend ostream& operator«(
ostream& os, Customer& a);
friend bool operator«
const Customer& a, const Customer& b);
332 11: Putting the STL to Work
There is no real problem with storing pointers in a container; you just have to
be aware that the default comparison operators will compare the pointers them-
selves, not the objects to which they point. Thus, if your application or container
requires the comparison of objects whose pointers are stored in a container, you
must define a comparison operator to correctly compare object pointers. For the
class Customer, such a comparison operator would look like this:
int operator«Customer* a, Customer* b)
{
return«*a) < (*b»;
class Transaction
public:
Transaction(): accountID(O), date(Date(O,O,O»,
transactionValue(O.O) {}
Transaction(int actID, Date dte, float transVal):
accountID(actID) , date(dte),
transactionValue(transVal) {}
virtual ostream& Print(ostream& os);
A Banking Problem 333
protected:
int accountID;
Date date;
float transactionValue;
};
The base class includes information common to most transactions such as the
account number, transaction date, and amount of money involved in the transac-
tion. The subclasses contain additional attributes and/or specialized methods to
handle specific transaction types. One such subclass, Depos it, is shown in
Listing 11.4.
This class adds no new attributes; however, it does redefine the virtual meth-
ods Print () and Process () to perform the functions appropriate to a de-
posit transaction. The code for these methods is typical of the code for the same
methods in all the classes derived from Transaction. (See Listing 11.5.)
iter = accountByNumber.find(accountID);
if(iter == accountByNumber.end(»
{
throw BadAccount(aCCountID);
}
(*iter).second.MakeDeposit(transactionYalue);
}
keyed on last name. We could traverse the map containing all the customers
looking for those with a particular last name, but this would involve a slow linear
search since the data would be organized by customer number, not name. If we
assume that this will be a fairly frequent operation, then it makes sense to speed
it up by providing a secondary index into the container of customers.
Last names are not unique, so a secondary index keyed on last names must be
able to handle duplicate keys. The customer objects are already stored in the
map customerByld, so there is no need to replicate them, and pointers to the
objects can be stored in the secondary index. The solution is to use a
multimap:
multimap<string, Customer*, less<string> >
customerByNamei
Enough preamble. Let's take a look at the entire program, as shown in List-
ing 11.6.
# ifndef BANK_H
#define BANK_H
#include <vector>
#include <string>
#include <map>
#include <utility>
#include <functional>
#include <iostream>
#include <algorithm>
class Accounti
class SavingsAccounti
class DailylnterestAccounti
class Customeri
class Transactioni
class Depositi
class withdrawli
class Transferi
class OpenAccounti
class CloseAccounti
class Balancei
class PrintTransactionsi
class CreateCustomeri
class JoinCustAccounti
336 11: Putting the STL to Work
class Date {
public:
Date(): day(O), month(O), year(O) {}
Date(int da, int mo, int yr): day(da),
month(mo), year(yr) {}
enum DateFormat{MDY, DMY, YMD, YDM};
int GetDay(){ return day;}
int GetMonth() { return month;}
int GetYear() { return year;}
friend ostream& operator«(ostream& os, Date& d);
friend istream& operator»(istream& is, Date& d);
private:
static DateFormat dateFormat;
static char dateSeparator;
int day, month, year;
} ;
class Transaction
public:
Transaction(): accountID(O), date(Date(O,O,O»,
transactionValue(O.O) {}
Transaction(int actID, Date dte, float transVal):
accountID(actID) , date(dte),
transactionValue(transVal) {}
virtual ostream& Print(ostream& os);
virtual void Process() {}
protected:
int accountID;
Date date;
float transactionValue;
} ;
class Account {
public:
Account(int ID, float bal = 0.0):
accountID(ID), balance(bal) {}
Account(vector<Customer*> custs);
float GetBalance() {return balance;}
float MakeDeposit(float amt);
float MakeWithdrawl(float amt);
void PrintTransactionList();
virtual void Addlnterest(Date& today) {}
void EraseTransactionList();
void AddTransaction(Transaction* t);
void AddCustomer(Customer& cust);
void DelCustomer(Customer& cust);
void DeleteAccountFromCustomers();
friend ostream& operator«(ostream& os,
Account& a);
friend bool operator«const Account& a,
const Account& b);
friend bool operator==(const Account& a,
const Account& b);
static void setDailyRate(float r)
{dailylnterestRate = r;}
static void setSavingsRate(float r)
{savingslnterestRate = r;}
protected:
vector<Customer*> customers;
vector<Transaction*> transactionList;
float balance;
340 11: Putting the STL to Work
class Customer {
private:
string firstName;
string lastName;
int customerID;
vector<Account*> accountList;
public:
Customer(string first, string last, int id):
firstName(first), lastName(last), customerID(id)
{}
Customer(): customerID(O) {}
string GetFirst(){ return firstName;}
string GetLast(){ return lastName; }
int GetCustomerID(){ return customerID; }
void AddAccount(Account& act);
void DelAccount(Account& act);
friend ostream& operator«(ostream& os,
Customer& a);
friend bool operator«const Customer& a,
const customer& b);
friend bool operator==(
const customer& a, const customer& b);
};
class BankException {
public:
BankException(){}
} ;
A Banking Problem 341
///////////////////////////////////////////////////
// GLOBALS
///////////////////////////////////////////////////
#endif
The details to note are the definition of the type Da te, the subclasses of the
Account class, and the various exceptions that can be thrown when errors are
encountered.
programs from paper listings. It has fallen into disuse since the introduction of
CRT terminals and full-screen text editors.
The cross-reference generator introduced here is designed to work with C
and C++ and assumes that all object names are unique throughout the program.
In other words, it cannot tell the difference between a variable declared in a
function and a variable of the same name declared in the global scope. To dif-
ferentiate the two, the program would have to parse the language-a task beyond
the scope of this simple example. It is also limited in that it will process strings
in quotes and inside comments. Both of these limitations can be removed with a
little extra effort.
Let us begin by examining the code in Listing 11.7 and then discuss how it
works.
#include <map>
#include <functional>
#include <vector>
#include <iostream>
#include <string>
#include <algorithm>
class Token {
public:
string tokenText;
long lineNum;
Token(): lineNum(OL) {}
Token(string text, long line): tokenText(text) ,
lineNum ( line) {}
} ;
class Scanner {
public:
Scanner(istream& is);
int operator() (Token& t);
private:
long currentLine;
istream* inputStream;
};
class XrefList {
public:
XrefList(){}
344 11: Putting the STL to Work
returneD);
}
int Scanner::operator()(Token& t)
{
char ch, buf[64];
int pt = 0;
inputStream->get(ch);
if(!(*inputStream» returneD);
while(!isalpha(ch» {
if(ch == '\n') currentLine++;
inputStream->get(ch);
if(!(*inputStream» returneD);
}
t.lineNum = currentLine;
while(isalpha(ch» {
buf[pt++] = Chi
inputStream->get(ch);
if(!(*inputStream» {
buf [pt] = '\0';
t.tokenText = string(buf);
return(l);
}
}
buf[pt] = '\0';
if(ch == '\n') currentLine++;
t.tokenText = string(buf);
return (1);
}
void maine)
{
Scanner scanner(cin);
map<string, XrefList, less<string> >
symtab;
map<string, XrefList, less<string> >::iterator
iter;
Token toke;
pair<map<string, XrefList,
346 11: Putting the STL to Work
while(scanner(toke» {
if(!KeyWord(toke.tokenText»
symPair = symtab.insert(
pair<const string, XrefList>
(toke.tokenText, XrefList(»);
«*symPair.first).second).
append(toke.lineNum);
}
for(iter=symtab.begin(); iter != symtab.end();
iter++) {
cout « (*iter).first « ", " «
(*iter).second « '\n';
The key to how the program works is in the class XrefList. This class has
a single attribute
vector<long> references;
that is used to store a list of the line numbers on which any single identifier oc-
curs. The symbol table itself is defined as a map in the function main.
map<string, XrefList, less<string> > symtab;
It uses a string as its key and stores values of type XrefList. The func-
tion main obtains tokens from the scanner and inserts them into the map used as
a symbol table. Since the map supports only unique keys, it does not insert the
same name twice, but returns an iterator referencing the current occurrence of
the key. If the key is not in the map, it is inserted, and a reference to it is re-
turned. Either way, a reference to the key and the object associated with the key
is returned.
It is interesting to examine how an object must be constructed before being
inserted into the map. The value to be inserted must be a pair whose first
member is the key and whose second member is the value associated with the
key. This is built using a pair constructor that specifies the types the pair is to
contain. Note that the key type must be declared to be a constant:
Symbol Tables 347
Using a map directly as a symbol table imposes the limitation that there can only
be a single scope. When we deal with real programming languages, we find that
they have nested scopes and allow identifiers to be duplicated in different
scopes. This implies that the symbol table itself must be hierarchical in nature to
reflect the structure of the programming language. Such a symbol table can be
built using a series of maps where each map corresponds to one of the scopes in
the program being parsed.
The fact that a scope can contain another scope means that the maps in the
symbol table must be able to hold other maps. Of course, they must also be able
348 11: Putting the STL to Work
to hold the types and variables declared in the scope. This requires a more com-
plex data structure to represent two different types of objects in a single map.
Furthermore, since we do not know beforehand what type of data will be stored
in the symbol table, we must write a series of class templates to allow for the
storage of different types.
The header file in Listing 11.8 presents a solution to this problem in the form
of a series of class templates.
Symbol Tables 349
Hfndef SYMTAB_H
#define SYMTAB_H
#include <map>
#include <string>
#include <iterator>
#include <vector>
//////////////////////////////////
/ / EXCEPTIONS
//////////////////////////////////
class SymTabError (
public:
SymTabError(){}
};
///////////////////////////////////////////////
// PATH - represents a path through the sym tab
// hierarchy as a vector of strings
///////////////////////////////////////////////
class SymTabPath {
protected:
vector<string> path;
public:
// public classes
typedef vector<string>: :iterator iterator;
350 11: Putting the STL to Work
// public methods
SymTabPath() {}
void append(string name){path.push_back(name);}
void clear() {path.erase(path.begin(),path.end(»;}
iterator begin() {return path.begin();}
iterator end() { return path.end();}
};
/////////////////////////////////////////////
// convenience functions to make paths
/////////////////////////////////////////////
SymTabPath mkPath(string sl)
{
SymTabPath p;
p.append(sl);
return p;
}
///////////////////////////////////////////////
// abstract base class for symbol table entries
///////////////////////////////////////////////
template <class contentType>
class SymTabEntry {
public:
SymTabEntry(){}
virtual void insert(string key, ContentType& entry)
{}
virtual void insert(string key) {}
virtual void insert(SymTabEntry& scope) {}
Symbol Tables 351
}i
//////////////////////////////////
// SYMBOL TABLE SCOPE
/////////////////////////////////
template <class ContentType>
class SymTabDatai
SymTabData<ContentType>* tmp;
tmp = new SymTabData<ContentType>(entry);
subScope.insert(pair<const string,
SymTabEntry<ContentType>* > (key, tmp»;
////////////////////////////////
/ / SYMBOL TABLE DATA
///////////////////////////////
template <class ContentType>
class SymTabData: public SymTabEntry<ContentType>
private:
ContentType data;
public:
SymTabData();
SymTabData(ContentType& entry): data(entry) {}
virtual ContentType GetData() {return data;}
ContentType operator*() {return data;}
virtual int ISScOpe(){return O;}
};
////////////////////////////////////
/ / USER-LEVEL SYMBOL TABLE
///////////////////////////////////
template <class ContentType>
class SymbolTable {
private:
SymTabScope<ContentType> subScope;
SymTabScope<ContentType>* currentScope;
public:
typedef SymTabScope<ContentType>: :iterator
iterator;
iterator begin() {return subScope.begin();}
iterator end() {return subScope.end();}
SymbolTable() {currentScope = &subScope;}
SymTabScope<ContentType> GetScope()
{
Symbol Tables 353
return subScope;
}
void CdRel(SymTabPath& path);
void CdAbs(SymTabPath& path);
SymTabEntry<ContentType>* FindRel(
SymTabPath& path);
SymTabEntry<ContentType>* FindAbs(
SymTabPath& path);
void InsertRel(SymTabPath& path, string name,
ContentType& entry);
void InsertAbs(SymTabPath& path, string name,
ContentType& entry);
void InsertRel(SymTabPath& path, string name,
SymTabScope<ContentType>& scope);
void InsertAbs(SymTabPath& path, string name,
SymTabScope<contentType>& scope);
void DeleteRel(SymTabPath& path, string name);
void DeleteAbs(SymTabPath& path, string name);
};
iter = subScope.find(name)i
if(iter == subScope.end(» throw SymTabNotFound()i
subScope.erase(iter)i
iter = subScope.find(name)i
if(iter == subScope.end(» throw SymTabNotFound()i
return (*iter).secondi
scope = currentScope;
for(pathlter=path.begin(); pathlter 1= path.end();
pathlter++) (
if(!lastWasScope) throw SymTabNotFound();
scope = scope->Find(*pathlter);
lastWasScope = scope->IsScope();
return (scope);
return (scope);
scope = currentScope;
for(pathIter=path.begin(); pathIter != path.end();
pa thIter++ ) (
scope = (scOpe->Find(*pathIter»;
if(!scope->IsScOpe() ) throw SymTabNotFound();
«SymTabScope<ContentType>*)scope)->insert(
name, entry);
scope = &subScope;
for(pathIter=path.begin(); pathIter != path.end();
pa thIter++ ) (
scope = (scope->Find(*pathIter»;
if(!scope->IsScope() ) throw SymTabNotFound();
}
«SymTabScope<ContentType>*)scope)->insert(
name, entry);
scope = currentScope;
for(pathIter=path.begin(); pathIter != path.end();
pathIter++) (
scope = (scope->Find(*pathIter»;
if(!scope->IsScope() ) throw SymTabNotFound()i
}
«SymTabScope<ContentType>*)scope)->insert(
name, newScope);
Symbol Tables 357
scope = &subScope:
for(pathlter=path.begin(); pathlter != path.end();
pathlter++) {
scope = (sCOpe->Find(*pathlter»;
if(!scope->IsScope() ) throw SymTabNotFound();
}
«SymTabScope<ContentType>*)scope)->insert(
name, newScope):
}
scope = &subScope;
358 11: Putting the STL to Work
#endif
provides methods to append a new scope onto the end of the path and to erase
the entire path. These methods invoke the methods push_back () and
erase ( ) on the underlying vector to perform their tasks.
The classes SymTabPa th, SymTabScope, and Symbol Table all pro-
vide iterators for accessing their contents. Since each of these enclose an STL
class that has its own iterator, we can use the iterators of the STL classes rather
than writing our own from scratch. This is done by using a typedef to define
the iterator for the symbol table classes to be the same as the iterator for the un-
derlying STL classes. The methods begin ( ) and end ( ) simply return the re-
sults of the methods of the same name belonging to the STL container.
A symbol table such as this is usually used as part of a compiler in conjunc-
tion with a parser. A compiler is normally broken into several components:
• A lexical analyzer (or scanner) that reads the input text and returns a stream
of tokens. The tokens represent the important objects in the text stream such
as keywords, operators, identifiers, parentheses, and the like.
• A parser that reads the stream of tokens from the lexical analyzer, checks that
the token stream adheres to the rules of the language, and recognizes
higher-level constructs such as expressions, statements, scopes, functions,
and the like.
• A symbol table used to store information on objects seen previously during
the compilation process. This is how the parser can determine that objects
are of the right type when they are used after being declared.
• A code generator that can generate machine code corresponding to the
operations recognized by the parser.
Parsers are very complex pieces of software and often recognize language
constructs in what we would consider a strange order. Consider the case of rec-
ognizing a scope in the C family of languages:
maine )
{
statement-l;
statement-2;
statement-n;
a statement list. It is only when the closing curly bracket is encountered that the
parser will recognize that an opening curly bracket followed by a statement list
and a closing curly bracket is a compound statement that forms its own scope.
Finally, it will realize that the compound statement was preceded by a function
header and conclude that a function is being defined.
If any of the statements in the compound statement are declarations, the iden-
tifiers will have to be placed in a scope. The problem is that the scope has not
been recognized at this point. Furthermore, it is only when the compound state-
ment is recognized that it can be connected with the function name on the first
line and the name of the function associated with the scope. Declarations within
the compound statement must be stored somewhere, so our program must be able
to create temporary scopes without a name until additional information becomes
available.
The solution to this problem is to have the parser create a nameless scope by
creating an instance of SymTabScope. Objects declared within the scope can
be placed into the symbol table scope, which can then be inserted into the larger
symbol table using these methods:
SymbolTable: : InsertRel(SymTabPath& path, string name,
ContentType& entry) or
SymbolTable::lnsertAbs(SymTabPath& path, string name,
ContentType& entry)
These methods allow a scope to be prepared outside the symbol table and
then inserted and named at the same time.
Examining the iterator for the symbol table, we see that it is the same as the
iterator for the SymTabScope, which is really a map iterator. This will return
pointers to all of the SymTabEntrys in the map. The fact that some of these
happen to be scopes in their own right makes no difference to the iterator-it
does not have the ability to traverse a subscope. As a result, the iterator as de-
fined will only traverse the topmost scope in the symbol table.
To correct this problem, we have to write our own iterator from scratch that
has the ability to traverse subscopes. The STL defines some basic iterator types
that are recognized by all the STL algorithms. Many of these algorithms are spe-
cialized to take advantage of the capabilities of the iterator they are passed. For
example, if the sort ( ) function is passed a bidirectional iterator, it cannot use
opera tor [ ] ( ) and will employ a different algorithm than if it is passed a ran-
dom access iterator where it can use operator [] ( ). Naturally, we want our
own iterators to behave just like, and be indistinguishable from, the iterators de-
fined by the STL.
Symbol Tables 361
To ensure that our own iterators are indistinguishable from the STL iterators,
we must do the following:
• Derive them from one of the STL iterator types (input, output, forward,
bidirectional, or random access).
• Provide all the methods required by the iterator type from which it is derived.
The code in Listing 11.9 is an iterator for the Symbol Table class that is
derived from the class forward_iterator, provides all the methods re-
quired of a forward iterator, and is able to traverse subscopes.
#ifndef SYMTAB_ITERATOR_H
#define SYMTAB_ITERATOR_H
#include <stack>
#include <iterator>
#include "symtab.h"
class SymTablterator:
forward_iterator<SymTabScope<ContentType>: : value_type,
SymTabScope<ContentType>: : difference_type>
protected:
struct StackEntry
{
SymTabScope<ContentType>* scope;
SymTabScope<ContentType>::iterator iter;
StackEntry(SymTabScope<ContentType>* scp,
SymTabScope<ContentType>: :iterator it):
scope(scp), iter(it) {}
StackEntry() {scope = o;}
};
stack<vector<StackEntry> > backStack;
SymTabScope<ContentType>* currScope;
SymTabScope<ContentType>: :iterator currlter;
char allDone;
public:
SymTablterator()
{
allDone = 0;
SymTablterator(SymTablterator& il)
362 11: Putting the STL to Work
backStack = il.backStack;
currScope = il.currScope;
currlter = il.currlter;
allDone = il.allDone;
SyrnTablterator(SyrnTabScope<ContentType>* scp,
SyrnTabScope<ContentType>: :iterator it)
currScope = scp;
currlter = it;
allDone = currlter currScope->end();
SyrnTablterator operator++(int)
{
SyrnbolTable<ContentType>: :iterator trnp *this;
if«*currlter).second->IsScope(»{
backStack.push(StackEntry(
Symbol Tables 363
currScope, currlter»;
currScope =
(SymTabScope<ContentType>*)
(*currlter).second;
currlter = currScope->begin();
return tmp; .
currlter++;
if(currlter == currScope->end(» {
if(backStack.empty(» allDone 1;
else {
currScope = backStack.top().scope;
currlter = backStack.top().iter;
currlter++;
return tmp;
}
SymTablterator& operator++()
{
if«*currlter).second->IsScope(»
backStack.push(
StackEntrY(CurrScope, currlter»;
currScope = (SymTabScope<ContentType>*)
(*currlter).second;
currlter = currScope->begin();
return *this;
currlter++;
if(currlter == currScope->end(» {
if(backStack.empty(» allDone 1;
else {
currScope = backStack.top().scope;
currlter = backStack.top().iter;
currlter++;
}
return *this;
}
};
#endif
Although much of the iterator code is unremarkable, the code for opera-
tor++ () warrants our attention. The symbol table itself is really a tree and
must be traversed in some organized fashion. The iterator presented here trav-
erses the symbol table in depth-first order, meaning that it traverses subscopes
within a scope before it finishes traversing the scope containing the subscope.
Whether this is the desired order or not depends on the application in which it is
used.
There are several techniques for traversing trees: recursion, iteration,
Deutsch-Schorr-Waite, and so on. The first point you have to realize about trav-
ersing a tree is that it is easier to go downward from the root to a leaf than it is to
find your way back up to the root. The reason for this is that most trees maintain
pointers from parent nodes to child nodes, but not pointers in the reverse direc-
tion. Thus, when you have finished traversing the subtree rooted at a node and
wish to return to the node's parent, you must have some way of finding the par-
ent of the node. The solution is to memorize your path down the tree by saving a
pointer to every node you visit on the way down from the root.
If you think about this problem, you will realize that every time you go down
a level in the tree a new node must be added to the path required to get back to
the root. Each time you ascend one level in the tree, the pointer to the parent
node is no longer needed and can be removed from the path. Thus, on descent,
node pointers are appended to the path, whereas on ascent, they are removed
from the same end of the path to which they were appended. These are the ac-
tions of a stack, which is the data structure required to save the path through a
tree so that it can be traversed successfully.
A recursive function is one that calls itself. Each time a function calls an-
other function, the state of the calling function is saved in a data structure called
the run-time stack. The state information includes the values of the local vari-
ables, the machine registers, and the location of the next instruction to be exe-
cuted. When the called function returns, the state values are popped off the
run-time stack, local variables are restored, and a branch is taken to the saved lo-
cation of the next instruction. The result is that the calling function continues to
execute as if nothing had interrupted it.
Recursion can be used to traverse a tree by ensuring that one of the local
variables saved on the run-time stack is a pointer to the tree node being visited.
This series of pointers saved on the run-time stack forms the back-path necessary
for the traversal to find its way back to the root of the tree. The popularity of re-
cursion for tree traversal is due to the fact that the programmer does not have to
worry about maintaining the stack-it is done automatically by the programming
language.
Symbol Tables 365
The problem with recursion is that the entire traversal is done with a single
recursive function call. This is very different from the way an iterator works.
Each time operator++ () is invoked on an iterator, it must advance one step
in the traversal and return a reference to the new node being visited. There is no
opportunity to take advantage of the run-time stack, so the iterator must maintain
its own stack internally.
The Deutch-Schorr-Waite algorithm is a clever way of traversing a tree with-
out using a stack. It manipulates the tree's own pointers so that they form a path
back up the tree. The trick is that after you follow a downward-pointing link,
you reverse it so that it points upward to the parent node. When you have fin-
ished visiting a node, you follow the upward link to the parent node and then re-
verse the link so that it points downward as it did originally.
An iterator could use the Deutch-Schorr-Waite algorithm if it had access to
the links in the tree. We could use this algorithm to traverse the symbol table,
but it would mean that only one traversal of the tree could be performed at once.
If two iterators were active at once, they would each be changing different links,
and mayhem would result. The only way to allow more than one iterator to be
active simultaneously is to have each of them store their own state internally in a
stack.
Examining the iterator, we see that it defines the structure StaekEntry
containing a scope pointer and the iterator for that scope. It also defines a stack
based upon a vector of StaekEntrys. operator++() uses a Sym-
TabSeope: : i tera tor to traverse a scope and examines each SymTabEn-
try returned by the iterator. If the entry is a scope, it saves the current scope
and iterator on the stack and sets its current scope and iterator pointers to be
those of the subscope. It iterates through this new scope until it reaches the past-
the-end iterator. Then it pops the previous scope and iterator off the stack and
continues to iterate through the previous level of the symbol table. When it tries
to pop the stack and finds it to be empty, the symbol table traversal is complete,
and it returns its own past-the-end iterator.
The Symbol Table iterator has a member allDone, used to indicate if it
is the past-the-end iterator. As long as this value is false, it is a valid iterator, but
when it becomes true, it is a past-the-end iterator. The iterator constructor
shown below will construct a past-the-end iterator when passed a scope and the
past-the-end iterator for that scope:
SymTablterator(SymTabSeope<ContentType>* sept
SymTabSeope<ContentType>::iterator it)
eurrSeope = sep;
366 11: Putting the STL to Work
currIter = it;
allDone = currIter == currScope->end();
In addition to providing the new iterator for the class, the SymbolTable
must redefine its own methods beg in ( ) and end ( ) to make proper use of the
new iterator, as in Listing 11.10.
iterator begin()
{
return SymTabIterator<ContentType>(
Symbol Tables 367
&subScope, subScope.begin(»;
}
iterator end()
{
return SymTablterator<ContentType>(
&subScope, sUbScope.end(»;
}
#include <set>
#include <functional>
#include <iostream>
main ( )
{
set<int, less<int> > s1;
set<int, less<int> >: : value_type value;
value = *(s1.find(1»;
cout « "value = " « value « endl;
returneD);
}
[r~-:--!-:-~-e-~-C-!--_--2------------------------------------------~]
If we want to create a new container type and have it behave in the same way
as the STL containers, we must define all of the appropriate types listed in Table
11.1. We must also provide all of the methods required by the container cate-
gory to which it belongs.
This has not been done in the case of the class SymbolTable, since it can
be argued that this is not a general-purpose class but is specialized for a specific
application. The key type is fixed to be a string and nothing else. To achieve
Symbol Tables 369
generality, the class would have to be defined to be a map of maps and both the
key type and the value type would need to be passed as template parameters.
The question that faces any software designer is, "Should this be defined as a
generic class or written as a special-purpose class?" The answer depends on the
probability of reuse of such a class, the additional effort required to make it ge-
neric, and any ease of use that will be sacrificed in the pursuit of generality. The
only time I have used a map of maps is as a symbol table, so I can argue that for
the applications that I usually write, the probability of reuse of a more generic
class would be low. These are questions that every designer must ask, since the
answers depend greatly on the type of applications being built.
12
The Story Continues
12.1 Introduction
In the mid-1980s, many of the workstation vendors had their own windowing
systems. These were all proprietary and mutually incompatible. In the late
1980s, the X Window System [SG86] appeared. This was produced by MIT,
placed in the public domain, and able to run on workstations from all the major
vendors. It was also designed to be easily ported to platforms on which it did
not run at that time.
In less than five years, the proprietary windowing systems had all but disap-
peared and all vendors were supporting the X Window System. It had become
the defacto industry standard. What led to this popularity? The factors can be
summarized as follows:
• an excellent design that could be extended,
• high dependability through years of use,
• cross-platform portability, and
• public domain so that any company could adopt it without high cost.
The Standard Template Library shares many of the qualities that made the X
Window System so successful. It is based on a solid design, is highly portable,
is in the public domain, and has been adopted as an ISO standard. As its usage
increases, it will become highly robust. Although most compiler vendors offer
their own C++ libraries, I believe these are destined for the same fate as the pro-
prietary windowing systems.
What the STL offers the programmer is not as revolutionary as the way it is
offered. The STL is based on a solid design in which all functions, containers,
and iterators work the same way. This is a strong point that makes the library
relatively easy to learn and to extend. Extensions from various vendors are
already appearing. All of these work with the current SlL components and are
much easier to learn and use than libraries developed independently.
I believe that the STL is destined for success and that the time to adopt it for
use in your applications is now. I think that you will be pleased that you made
this decision.
12.3 Summary
In the 1950s, libraries of reusable software components were in common use.
One of the more successful libraries of this period was SHARE, a public-domain
library of functions contributed by individuals and organizations. This library
was able to succeed due to the limited number of languages and data types in use
at the time. Over the years, such organizations of programmers fell into disuse
as major hardware vendors assumed the responsibility of providing all system
software to accompany the hardware they sold. These vendors paid little atten-
tion to reusable components and, as languages and operating systems evolved,
the old ones fell into disuse.
The C family of languages is available on virtually every platform and is one
of the most portable families of programming languages. During the 1980s the
family gained immense popularity, and this trend continued with the adoption of
object-oriented techniques and C++. Once templates were added to C++, the
programming community again had the means to produce reusable software
components that could be used by a significant fraction of its members.
The introduction of the STL brings reusable components to the C++ commu-
nity. Whereas the SHARE effort had been an uncoordinated collection of func-
tions championed by individuals, the STL is an organized, well-designed
framework on which more extensive libraries can be based. Furthermore, it is
sanctioned by standards organizations and must be supported by all compiler
vendors who wish to claim compliance with standards.
374 12: The Story Continues
The current version of the STL provides a well-designed framework for a li-
brary of reusable components as well as a collection of useful data structures and
algorithms. Over time, this collection will be augmented as vendors provide li-
braries to extend the capabilities of the STL. This will probably lead to the clas-
sification of algorithms and data structures and the adoption of a standard
taxonomy.
The result could well revolutionize the practice of programming as we know
it today. The programmer of the future would no more consider writing low-
level algorithms by hand than the programmers of today would consider building
a loop from if statements and gatos. The training of programmers will change
from teaching them how to code low-level algorithms to teaching them the the-
ory of various algorithms and the names of reusable implementations of those al-
gorithms. This has already happened in the area of numerical methods, where
extensive libraries have been available for some time, and will be extended to
more general algorithms and data structures.
The STL brings to fruition one of the most sought-after goals of computer
science in the last decade-software reuse. The programmer's job will get eas-
ier, and both productivity and maintainability will be enhanced. Software engi-
neering comes a step closer to understanding how computer programs should be
written and making programs robust and dependable.
Appendix A
STL Header Files
The following table lists the STL header files that you will need to include in
programs using the STL. This is the list used by the c++ standard. Other imple-
mentations might use other header files:
This appendix is a reference to all of the types, functions, and containers in the
Standard Template Library. It is designed to be used in conjunction with the
main text as a handy reference for the working programmer. The descriptions in
this reference are much briefer than those provided in the main text, which
should be consulted when a more detailed discussion is required.
The reference is organized in a purely alphabetical fashion without regard for
whether an object is a function or container. This will make it easier to find
functions and containers since there are no sections within the reference.
Header files for implementations conforming to the C++ standard are en-
closed in angle brackets «». Header files for the HP implementation are en-
closed in square brackets ([]). Header files for other implementations are in
curly brackets ({}). An empty set of brackets indicates that the STL component
is not available in that implementation.
accumulate
TYPE: function HEADER: <numeric> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class T>
T accumulate(Inputlterator first,
Inputlterator last,
T init);
DESCRIPTION: The first form sums the values from first up to, but not in-
cluding, last, adds the value of init, and returns the result. The second
form allows the user to supply a binary operation that will be used in place of
addition.
adjacent_difference
TYPE: function HEADER: <numeric> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class Outputlterator>
outputlterator adjacent_difference(
Inputlterator first,
Inputlterator last,
Outputlteratorresult);
adjacent_find
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator>
Inputlterator adjacent_find(Inputlterator first,
B: The STL Reference 379
Inputlterator last) ;
DESCRIPTION: These functions find the first pair of duplicate elements in the
range first to last. If a duplicate is found, then the functions return an
iterator referring to the first element in the duplicate. If no duplicate is
found, an iterator equal to last is returned. In the first form ofthe function,
opera tor== is used to compare elements. The second form allows the
user to specify an arbitrary binary predicate function to use to compare
elements.
advance
TYPE: function HEADER: <iteratof> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class Distance>
void advance(Inputlterator& i,
Distance n);
class auto_ptr;
}
Public Members:
typedef X auto_ptr<X>::element_type;
Public Methods:
explicit auto_ptr<x>: :auto-ptr(X* p =0) throw();
auto_ptr<X>: :auto-ptr(const auto_ptr&) throw();
template<class Y>
auto_ptr<X>: :auto-ptr(const auto_ptr<Y>&) throw();
These are the class constructors. The first associates the auto_ptr with the
parameter. If no parameter is provided, a null pointer is assumed. The
auto_ptr never takes ownership of a null pointer. The second and third
forms are the copy constructors, which copy the value of the pointer refer-
enced by the parameter and assume ownership if the pointer is non-null. The
third form is able to convert y* to X *.
This operator assigns the parameter to *this. If the value of the pointer refer-
enced by the parameter is non-null, *this will assume ownership of the
pointer. The object referenced by the pointer will be destroyed when the
auto_ptr that owns it is destroyed.
auto_ptr<X>: :-auto-ptr();
The class destructor. This will destroy the object referenced by the encapsulated
pointer if *this is the owner of the encapsulated pointer.
DESCRIPTION: Unlike other iterators, when assigned a value the insert itera-
tors insert a new value into a container rather than overwriting an existing
value. The back_insert_i tera tor accomplishes this by invoking
push_back ( ) on the underlying container. Of course, this means that the
underlying container must support the push_back () operation. Insert it-
erators are useful with the STL algorithms to copy values to a container with-
out having to worry if sufficient space exists.
382 B: The STL Reference
Public Members:
back_insert_iterator<Container>::container_typei
Public Methods:
back insert_iterator<Container>&
back_insert_iterator<Container>: : operator= (
type name Container: :const_reference value)i
back_insert_iterator<Container>&
back_insert_iterator<Container>::operator++()i
back_insert_iterator<Container>
back_insert_iterator<Container>: :operator++(int)i
back_insert_iterator<Container>&
back_insert_iterator<Container>: :operator*()i
back inserter
TYPE: function HEADER: <iterator> [iterator.h]
namespace std{
template <class Container>
B: The STL Reference 383
back_insert_iterator<Container>
back_inserter(Container& x);
}
Public Methods:
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::append(
const basic_string<charT,traits,Allocator>& str);
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::append(
const charT* S)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::append(
Inputlterator first,
Inputlterator last);
Appends a string to the current string. The first form appends the entire parame-
ter string. The second form appends n elements from str, starting at pos
or until the end of the string is reached, whichever comes first. It can throw
length_error or out_of_range exceptions depending on the parame-
ter values. The third form appends n elements from s. The fourth form ap-
pends the entire array s. The fifth form appends n copies of c. The final
form appends the elements from first up to last.
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::assign(
Inputlterator first,
Inputlterator last);
Assigns a string to the current string. The first fonn assigns the entire parameter
string. The second form assigns n elements from str, starting at pos or
until the end of the string is reached, whichever comes first. It can throw an
out_of_range exception if pos is greater than the size of the parameter
string. The third fonn assigns n elements from s. The fourth fonn assigns
the entire array s. The fifth fonn assigns n copies of c. The final fonn as-
signs the elements from first up to last.
reference
basic_string<charT,traits,Allocator>::at(
size_type pos);
const_reference
basic_string<charT,traits,Allocator>::at(
size_type pos) const;
explicit
basic_string<charT,traits,Allocator>::basic_string(
const Allocator& a = Allocator(»;
basic_string<charT,traits,Allocator>::basic_string(
const basic_string<charT,traits,Allocator>& str,
size_type pos = 0,
size_type n = npos,
const Allocator& a = Allocator(»;
basic_string<charT,traits,Allocator>::basic_string(
const charT* s,
size_type n,
const Allocator& a = Allocator(»;
basic_string<charT,traits,Allocator>::basic_string(
const charT* s,
const Allocator& a = Allocator(»;
basic_string<charT,traits,Allocator>::basic_string(
size_type n,
386 B: The STL Reference
charT c,
const Allocator& a = Allocator(»:
basic_string<charT,traits,Allocator>::basic_string(
Inputlterator begin,
Inputlterator end,
const Allocator& a = Allocator(»:
These are the class constructors. The first form constructs an empty string. The
second form constructs a basic_string and initializes it to contain the
elements from str, starting at pos and continuing for n elements or to the
end of str, whichever is reached first. The third form constructs a ba-
sic_string, initializing it with the first n characters from s. The fourth
form constructs a basic_string, initializing it with the contents of s.
The fifth form initializes a basic_string to contain n copies of c. The
final form constructs a basic_string and initializes it to contain the ele-
ments from begin up to end. None ofthe pointers to arrays can be NULL.
iterator
basic_string<charT,traits,Allocator>::begin():
const_iterator
basic_string<charT,traits,Allocator>::begin():
size_type
basic_string<charT,traits,Allocator>::capacity()
const:
Returns the size of the currently allocated storage for the string, which will be
greater than or equal to s i z e ( ) .
void basic_string<charT,traits,Allocator>::clear():
Removes all elements from the string, invalidating all pointers, references, and
iterators.
const charT*
basic_string<charT,traits,Allocator>::c_str():
int basic_string<charT,traits,Allocator>::compare(
const basic_string<charT,traits,Allocator>& str)
B: The STL Reference 387
const;
int basic_string<charT,traits,Allocator>::compare(
size_type posl,
size_type nl,
const basic_string<charT,traits,Allocator>& str)
const;
int basic_string<charT,traits,Allocator>::compare(
size_type posl,
size_type nl,
const basic_string<charT,traits,Allocator>& str,
size_type pos2,
size_type n2)
const;
int basic_string<charT,traits,Allocator>::compare(
const charT* s) const;
int basic_string<charT,traits,Allocator>::compare(
size_type posl,
size_type nl,
const charT* s,
size_type n2 = npos)
const;
size_type
basic_string<charT,traits,Allocator>::copy(
charT* s,
size_type n,
size_type pos 0)
const;
388 B: The STL Reference
Copies n elements, or until the end of string, from the string starting at pos to
the array s. This can throw an out_of_range exception if pos is greater
than the length of the string.
const charT*
basic_string<charT,traits,Allocator>::data();
Returns a reference to the first element in the string. If the string is empty, a
pointer that cannot be dereferenced is returned.
iterator basic_string<charT,traits,Allocator>::end();
const_iterator
basic_string<charT,traits,Allocator>::end();
bool
basic_string<charT,traits,Allocator>::empty() const;
basic_string&
basic_string<charT,traits,Allocator>::erase(
size_type pos = 0,
size_type n = npos);
iterator
basic_string<charT,traits,Allocator>::erase(
iterator position);
iterator
basic_string<charT,traits,Allocator>::erase(
iterator first,
iterator last);
Removes the indicated element(s) from the string. The first form removes the n
elements, beginning at pos or until the end of the string is reached. It throws
an out_of_range exception if pos is greater than the length ofthe string.
The method returns *this. The second form removes the single element
referenced by posi tion and returns an iterator referencing the element af-
ter the one removed or a past-the-end iterator if the last element is removed.
The third form removes the elements from first up to last and returns an
iterator referencing the element after the last one removed or a past-the-end
iterator if the last element was removed.
B: The STL Reference 389
size_type
basic_string<charT,traits,Allocator>::find(
const basic_string<charT,traits,Allocator>& str,
size_type pos = 0)
const;
size_type
basic_string<charT,traits,Allocator>::find(
const charT* s,
size_type pos,
size_type n)
const;
size_type
basic_string<charT,traits,Allocator>::find(
const charT* s,
size_type pos = 0)
const;
size_type
basic_string<charT,traits,Allocator>::find(
charT c,
size_type pos = 0)
const;
Finds the first location of a substring within the string. It either returns the loca-
tion of the first element of the substring or returns bas ic_str ing: : npos
if the substring is not found. The second form searches for the n elements
from s or until the end of string. The search begins at pos. The third form
searches for all the elements of s starting at pos. The fourth form searches
for the single element c starting at pos.
size_type basic_string<charT,traits,Allocator>::
find_first_of(
const basic_string<charT,traits,Allocator>& str,
size_type pos = 0)
const;
size_type basic_string<charT,traits,Allocator>::
find_first_of(
const charT* s,
size_type pos,
size_type n)
const;
390 B: The STL Reference
size_type basic_string<charT,traits,Allacatar>::
find_first_of (
canst charT* s,
size_type pas 0)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_first_of (
charT c,
size_type pas 0)
canst;
Returns the first position at which any of the elements in the parameter occur in
the string. If none of the elements of the parameter occur in the string,
basic_string: : npas is returned. The first form searches the string for
any of the elements in str from pas onward. The second form searches the
string for the n elements in s, starting at pas in the string or until the end of
s. The third form searches the string from pas for all elements of s. The
fourth form searches the string, starting at pas, for the single element c.
size_type basic_string<charT,traits,Allacatar>::
find_last_of(
canst basic_string<charT,traits,Allacatar>& str,
size_type pas = npos)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_last_of(
canst charT* s,
size_type pas,
size_type n)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_last_of (
canst charT* s,
size_type pas npas)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_last_of (
charT c,
size_type pas npas)
canst;
B: The STL Reference 391
Finds the last element in the string that matches one of the elements in the pa-
rameter. It returns the position or basic_string: : npos if there is no
such element. The first form searches the string for any of the elements in
str from pos onward. The second form searches the string for the n ele-
ments in s, starting at pos in the string or until the end of s. The third form
searches the string from pos for all elements of s. The fourth form searches
the string, starting at pos, for the single element c.
size_type basic_string<charT,traits,Allocator>::
find_first_not_of(
const basic_string<charT,traits,Allocator>& str,
size_type pos = 0)
const;
size_type basic_string<charT,traits,Allocator>::
find_first_not_of(
const charT* s,
size_type pos,
size_type n)
const;
size_type basic_string<charT,traits,Allocator>::
find_first_not_of(
const charT* s,
size_type pos 0)
const;
size_type basic_string<charT,traits,Allocator>::
find_first_not_of(
charT c,
size_type pos 0)
const;
Finds the position of the first element in the string that does not occur in the pa-
rameter string. If such an element exists, its position is returned, otherwise
basic_string: : npos is returned. The first form searches the string for
any of the elements not in s tr from pos onward. The second form searches
the string for the n elements not in s, starting at pos in the string or until the
end of s. The third form searches the string from pos for all elements not in
s. The fourth form searches the string, starting at pos, for the element not
equal to the single element c.
392 B: The STL Reference
size_type basic_string<charT,traits,Allacatar>::
find_last_not_of(
canst basic_string<charT,traits,Allacatar>& str,
size_type pas = npos)
canst;
size_type basic_string<charT,traits,Allacator>::
find_last_not_of(
canst charT* s,
size_type pas,
size_type n)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_last_not_of(
canst charT* s,
size_type pas npas)
canst;
size_type basic_string<charT,traits,Allacatar>::
find_last_not_of(
charT c,
size_type pas npas)
canst;
Finds the last element in the string that is not in the parameter string. It returns
the position of the element found or basic_string: : npas if there is no
such element. The first form searches the string for any of the elements not
in str from pas onward. The second form searches the string for the n ele-
ments not in s, starting at pas in the string or until the end of s. The third
form searches the string from pas for all elements not in s. The fourth form
searches the string, starting at pas, for the element not equal to the single
element c.
allocatar_type
basic_string<charT,traits,Allacatar>::get_allocator()
canst;
basic_string<charT,traits,Allacatar>&
basic_string<charT,traits,Allocatar>::insert(
size_type pasl,
canst basic_string<charT,traits,Allacatar>& str);
B: The STL Reference 393
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::insert(
size_type posi,
const basic_string<charT,traits,Allocator>& str,
size_type pos2,
size_type n)i
basic_string<charT,traits,Aiiocator>&
basic_string<charT,traits,Allocator>::insert(
size_type pos,
const charT* s,
size_type n)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::insert(
size_type pos,
const charT* S)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::insert(
size_type pos,
size_type n,
charT c) i
iterator
basic_string<charT,traits,Aliocator>::insert(
iterator p,
charT c = charT ( ) ) i
void
basic_string<charT,traits,Allocator>::insert(
iterator p,
size_type n,
charT c) i
void
basic_string<charT,traits,Allocator>::insert(
iterator p,
Inputlterator first,
Inputlterator last)i
394 B: The STL Reference
Inserts the parameter string into the string immediately before the indicated posi-
tion. It returns *this, the string itself, when a nonvoid return type is indi-
cated. Where the number of elements from the parameter string is indicated,
it will insert that number of elements or until the end of the parameter string,
whichever comes first. The first form inserts the entire contents of str. The
second form inserts the n elements of str beginning at pos2. The third
form inserts the first n elements of s. The fourth form inserts the entire array
s. The fifth form inserts n copies of the element c. The sixth form inserts
the single element c. The seventh form inserts n copies of the element c.
The eighth form inserts the elements from first up to last.
size_type
basic_string<charT,traits,Allocator>::length() const;
size_type
basic_string<charT,traits,Allocator>::max_size()
const;
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::replace(
size_type posl,
size_type nl,
const basic_string<charT,traits,Allocator>& str,
size_type pos2,
size_type n2);
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::replace(
size_type pos,
size_type nl,
const charT* s,
size_type n2);
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::replace(
B: The STL Reference 395
size_type pos,
size_type n1,
const charT* S)i
basic_string<charT,traits,A11ocator>&
basic_string<charT,traits,Allocator>::replace(
size_type pos,
size_type n1,
size_type n2,
charT C)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,A11ocator>::replace(
iterator i1,
iterator i2,
const basic_string<charT,traits,A11ocator>& str)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::replace(
iterator i1,
iterator i2,
const charT* s,
size_type n) i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,A11ocator>::replace(
iterator i1,
iterator i2,
const charT* S)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,A11ocator>::replace(
iterator i1,
iterator i2,
size_type n,
charT C)i
basic_string<charT,traits,Allocator>&
basic_string<charT,traits,Allocator>::replace(
iterator i1,
iterator i2,
Inputlterator j1,
Inputlterator j2)i
396 B: The STL Reference
Replaces the indicated range of elements in the string with the replacement ele-
ments specified in the parameters. Methods that use a parameter indicating
the number of elements will consume this number unless the end of the string
is reached. In such a case, the maximum number of elements possible will be
used. Use of these methods invalidates pointers, references, and iterators.
The first form replaces the nl elements in the string from posl onward with
the elements of str. The second form replaces the nl elements in the string
from posl onward with the n2 elements of str from pos2 onward. The
third form replaces the nl elements from pos onward with the n2 elements
of the array s. The fourth form replaces the nl elements of the string from
pos onward with all the elements in s. The fifth form replaces the nl ele-
ments of the string from pos onward with the element c. The sixth form re-
places the nl elements of the string from pos onward with n2 copies of the
element c. The seventh form replaces the elements from il up to i2 by the
contents of str. The eighth form replaces the elements from il up to i2
with n elements of s. The ninth form replaces the elements from il up to
i2 by the contents of s. The tenth form replaces the elements from il up to
i2 by n copies of the element c. The eleventh form replaces the elements
from i1 up to i2 by the elements from j 1 up to j 2.
void basic_string<charT,traits,Allocator>::reserve(
size_type res_arg=O);
This is used prior to a change in the size of the string to ensure that adequate
memory is allocated. After it is executed, capaci ty ( ) >= res_argo All
references, pointers, and iterators are invalidated.
void basic_string<charT,traits,Allocator>::resize(
size_type n, charT c);
Alters the size of the string. If n is less than or equal to the current size of the
string, the string is truncated to the first n characters. If n is greater than the
size of the string, a longer string is created whose first size ( ) elements are
a copy of the current string contents and whose remaining elements are ini-
tialized to c. n must be less than or equal to max_s i z e ( ) .
size_type
basic_string<charT,traits,Allocator>::rfind(
const basic_string<charT,traits,Allocator>& str,
size_type pos = npos)
const;
B: The STL Reference 397
size_type
basic_string<charT,traits,Allocator>::rfind(
const charT* s,
size_type pos,
size_type n)
const;
size_type
basic_string<charT,traits,Allocator>::rfind(
const charT* s,
size_type pos = npos)
const;
size_type
basic_string<charT,traits,Allocator>::rfind(
charT c,
size_type pos = npos)
const;
Searches in the reverse direction for the first occurrence of the subsequence
specified by the parameter. It begins seraching at pos, which is an offset
from the end of the string. It returns the position where the matching sub-
sequence begins in the string or bas ic_string: : npos if there is no oc-
currence. The first form searches for the contents of s tr. The second form
searches for the first n elements of s or until the end of s if n is greater than
the length of s. The third form searches for the entire array s. The fourth
form searches for the single element c.
size_type
basic_string<charT,traits,Allocator>::size() const;
basic_string
basic_string<charT,traits,Allocator>::substr(
size_type pos = 0,
size_type n = npos)
const;
Returns a subsequence of the string whose first element begins at pos and has n
elements or until the end of the string is reached. It can throw an out_of_-
range exception if pos is greater than the length of the string.
void basic_string<charT,traits,Allocator>::swap(
basic_string<charT,traits,Allocator>& s);
398 B: The STL Reference
Swaps the contents of the string with the string s so that each has the content of
the other.
Nonmember Functions:
template<class charT, class traits,
class Allocator>
basic_string<charT,traits,Allocator>
operator+(
const basic_string<charT,traits,Allocator>& lhs,
const basic_string<charT,traits,Allocator>& rhs) ;
Extracts a string from is into str until either is. width () elements are ex-
tracted, str. max_size ( ) elements are extracted, EOF is encountered, or
a space is found in the input stream. isspace() is used to determine if the
element is a space.
402 B: The STL Reference
The first form extracts elements from is into str, replacing the contents of
str, until EOF is encountered, is . max_size ( ) elements are extracted,
or de lim is encountered. The second form does the same thing but uses '\01
as a delimiter.
binary_function
TYPE: struct HEADER: <functional> [function.h]
namespace std{
template <class Argl, class Arg2, class Result>
struct binary_function;
}
DESCRIPTION: This is a base struct from which all the function objects that
are binary_functions are derived. It models a function that requires
two parameters. Any function conforming to the definition of a bi-
nary_function must define first_argument_type, sec-
ond_argument_type, and resul t_type.
Public Members:
binary_function<class Argl, class Arg2,
class Result>::
first_argument_type;
binary_negate
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class Predicate>
class binary_negate
public binary_function<
typename Predicate: :first_argument_type,
typename Predicate: :second_argument_type,
bool>;
}
Public Methods:
explicit binary_negate<predicate>: : binary_negate (
const Predicate& pred);
404 B: The STL Reference
This is the operator ( ) which is the way all function objects are invoked.
This returns the negation of the result returned by pred (x, y) .
bind1st
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class Operation, class T>
binderlst<Operation> bindlst(
const Operation& op,
const T& x);
DESCRIPTION: This creates a new function object that is identical to op, but
that has x bound as the fIrst parameter of the function object op.
B: The STL Reference 405
bind2nd
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class Operation, class T>
binder2nd<Operation> bind2nd(
const Operation& op,
const T& Xli
}
DESCRIPTION: This creates and returns a function object that accepts two pa-
rameters. When invoked, this function object will apply op to the first pa-
rameter, using x as the second parameter, and return the result.
binder1st
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class Operation>
class binderlst
public unary_function<
typename operation::second_argument_type,
typename Operation: :result_type>i
}
Public Methods:
binderlst<Operation>: :binderlst(
const Operation& x,
const Operation: :first_argument_type& y)i
406 B: The STL Reference
binder2nd
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class Operation>
class binder 2nd
public unary_function<
type name Operation: :first_argument_type,
type name Operation: : result_type>;
}
Public Members:
binder2nd<Operation>: : value;
Public Methods:
binder2nd<Operation>: : binder2nd (
const Operation& x,
const Operation: :second_argument_type& y);
B: The STL Reference 407
bitset
TYPE: class HEADER: <bitset> [bvector.h]
names pace std {
template<size_t N>
class bitset;
Public Members:
bitset<N>: : reference;
Public Methods:
bool bitset<N>: :any() const;
The first form negates all of the bits in *this. The second form negates the bit
in position pos. out_of_range will be thrown if pos is invalid.
Replaces each bit with the bit pos positions before it. Bits whose position is
less than pos are replaced by zero.
Replaces each bit with the bit pos positions after it. Bits whose position is
greater than or equal to N - pos are replaced by o.
B: The STL Reference 409
Returns true if every bit in *this equals the corresponding bit in rhs.
The first form sets all the bits to O. The second form sets the bit at position pas
to O. out_of_range will be thrown if pas is invalid.
The first form sets all bits to 1. The second form sets the bit at position pas to 0
or 1, depending on whether val is zero or nonzero. out_of_range will
be thrown if pas is invalid.
Converts the bitset to a character string with 1 bits represented as the character
"1" and 0 bits represented as "0". The order of the characters is the reverse
of the order of the bits.
410 B: The STL Reference
Converts the bits to a long value. Throws overflow_error if the bits can-
not be represented as a long.
Nonmember Functions:
bitset<N> operator&(const bitset<N>& Ihs,
canst bitset<N>& rhs) ;
Reads up to N single byte characters from the stream terminating when Ncharac-
ters have been read, end-of-file is found, or a character that is neither 0 nor 1
is found. The result is converted to a bitset and returned in x.
char traits
TYPE: struct HEADER: <string> []
B: The STL Reference 411
namespace std{
template<class T>
struct char_traitsi
}
DESCRIPTION: A structure defining types and methods that allow string op-
erations to be performed on a string-like type. Specializations are provided
for actual types commonly used to represent characters. It is used as a tem-
plate parameter of other classes, such as basic_string, so that they can
handle different character representations.
Public Members:
A type that can represent all the valid characters in T as well as an end-of-file
character.
Public Methods:
void char_traits<T>: :assign(char_type& X,
canst char_type& y)i
Assigns X y.
412 B: The STL Reference
char_type* char_traits<T>::assign(char_type* s,
size_t n,
char_type c);
char_type *
char_traits<T>: : copy (char_type* sl,
const char_type* s2,
size_t n);
int_type char_traits<T>::eof();
bool
char_traits<T>: :eq_int_type(const int_type& c,
const int_type& d);
char_type *
char_traits<T>: :find(const char_type* s,
size_t n,
const char_type& c);
char_type*
char_traits<T>: :move(char_type* sl,
const char_type* s2,
size_t n);
Copies the first n values from s2 to s1. sl and s2 can overlap. It returns sl
+ n.
Returns c if c is not the end-of-file (EOP) value; otherwise, returns a value that
is not the EOF value.
char_type
char_traits<T>: :to_char_type(const int_type& i);
compose1
TYPE: adaptor HEADER: <> [function.h]
template <class Operation 1 , class Operation2>
unary_compose<Operationl, Operation2>
composel(const Operation1& op1,
const Operation2& op2);
414 B: The STL Reference
compose2
TYPE: adaptor HEADER: <> [function.h]
template <class Operationl, class Operation2,
class Operation3>
binary_compose<Operationl, Operation2, Operation3>
compose2(const Operationl& opl,
const Operation2& op2,
const Operation3& op3);
DESCRIPTION: This combines the three separate function objects into a single
function object. The resultant function applies the parameter functions in the
order opl (op2 (x), op2 (x) ). This is only in the HP implementation
and has been removed from the C++ standard.
canst iterator
TYPE: typedef REQUIRED BY: all containers
canst reference
TYPE: typedef REQUIRED BY: all containers
copy
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class outputlterator>
Outputlterator copy(Inputlterator first,
Inputlterator last,
Outputlterator result);
DESCRIPTION: This copies all elements from first up to but not including
last to the location referred to by resul t. It returns an iterator that refers
to the location one past the end of the destination. The source and destina-
tion ranges may overlap only if resul t <= first.
copy_backward
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallteratorl,
class Bidirectionallterator2>
Bidirectionallterator2
copy_backward (Bidirectionallteratorl first,
Bidirectionallteratorl last,
Bidirectionallterator2 result);
}
DESCRIPTION: This copies the sequence from first up to but not including
last to a location whose upper end is indicated by result. It returns an
iterator that references the last element copied, which will be the start of the
sequence in the destination. The source and destination ranges may overlap
only if resul t >= last.
count
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
416 B: The STL Reference
namespace std{
template <class Inputlterator, class T>
typename
iterator_traits<Inputlterator>: : difference_type
count(Inputlterator first,
Inputlterator last,
const T& value);
}
DESCRIPTION: Counts all the elements in the range first up to last that
satisfy the predicate pred. No order of the elements is assumed, and the en-
tire range is searched.
deque
TYPE: class HEADER: <deque> [deque.h]
names pace std {
template <class T, class Allocator = allocator<T> >
class deque;
B: The STL Reference 417
Public Members:
deque<T, Allocator>: : allocator_type;
Public Methods:
void deque<T, Allocator>: :assign(
size_type n,
const T& value);
template<class Inputlterator>
void deque<T, Allocator>: :assign(
Inputlterator first,
Inputlterator last);
These methods erase the content of the deque and replace it with new values
specified by the parameters. The first form fills the deque with n copies of
value. The second form inserts the values from first up to last.
Returns a reference to the nth element of the deque. n must be less than
size().
The first form constructs an empty deque. The second form constructs a deque
containing n copies of value. The third form constructs a deque containing
the elements from first up to last. The fourth form is the copy
constructor.
Returns an iterator that is one past the last element in the deque.
iterator first,
iterator last) ;
Deletes the element at the indicated position from the deque. The second form
deletes all elements in the range from first up to last. An iterator refer-
encing the element after the last element erased is returned. Only iterators,
pointers, and references to the erased elements are invalidated.
Inserts the element x just before pos i tion. The first form returns an iterator
that references the element just inserted. The second form inserts the ele-
ments from first up to last just before pos i tion. The third form in-
serts n copies of the element x just before pos i tion.
Returns the maximum number of elements that can be stored in the deque.
Returns a reference to the nth element of the deque. n must be less than
size( ).
Returns an iterator that is one past the end of the deque and can be used as the
starting value for a reverse iterator.
Returns an iterator that can be used to find the end for a reverse iteration.
Changes the size of the deque so that it contains size members. If the deque
has less than size elements, new elements are appended to make it the de-
sired length, with the new elements assigned value. If the deque has more
than size elements, elements are removed from the end to make it the de-
sired length. If the deque is the desired length, nothing is done.
Swaps the contents of the object deque with the deque x so that each has the
contents of the other.
Nonmember Functions:
template <class T, Allocator>
bool operator==(const deque<T, Allocator>& x,
const deque<T, Allocator>& y);
difference_type
TYPE: typedef REQUIRED BY: all containers
DESCRIPTION: A typedef provided by all containers defining the type for the
difference of two iterators.
distance
TYPE: function HEADER: <iteratoD [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator>
type name
iterator_traits<Inputlterator>::difference_type
distance(Inputlterator first,
Inputlterator last)i
424 B: The STL Reference
distance_type
TYPE: function HEADER: <> [iterator.h]
TIME: constant SPACE: constant
template <class T, class Distance>
inline Distance* distance_type (
const input_iterator<T, Distance>&);
divides
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct divides: binary_function<T, T, T> {
T operator() (const T& x, const T& y) const;
};
equal
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class InputIteratorl,
class InputIterator2>
bool equal(InputIteratorl firstl,
InputIteratorl lastl,
InputIterator2 first2);
fill
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlterator, class T>
void fill(Forwardlterator first,
Forwardlterator last,
const T& value);
}
fill n
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class outputlterator, class Size,
class T>
void fill_n(Outputlterator first,
Size n,
const T& value) ;
find
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class T>
Inputlterator find(Inputlterator first,
Inputlterator last,
const T& value);
find end
TYPE: function HEADER: <algorithm> []
TIME: (lastl - frrstl) * (last2 - first2) SPACE: constant
namespace std{
template <class Forwardlteratorl,
class Forwardlterator2>
Forwardlteratorl find_end (
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
428 B: The STL Reference
Forwardlterator2 last2) ;
template <class Forwardlteratorl,
class Forwardlterator2,
class BinaryPredicate>
Forwardlteratorl find_end (
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2,
BinaryPredicate comp) ;
find- first- of
TYPE: function HEADER: <algorithm> []
TIME:n*m SPACE: constant
namespace std{
template<class Forwardlteratorl, class
Forwardlterator2>
Forwardlteratorl find_first_of(
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2);
template<class Forwardlteratorl, class
Forwardlterator2, class BinaryPredicate>
Forwardlteratorl find_first_of(
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2,
B: The STL Reference 429
BinaryPredicate pred) ;
}
find if
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class Predicate>
Inputlterator find_if(Inputlterator first,
Inputlterator last,
Predicate pred);
}
for each
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlterator, class Function>
Function for_each(Inputlterator first,
Inputlterator last,
Function f);
}
430 B: The STL Reference
forward iterator
TYPE: structure HEADER: <> [iterator.h]
template <class T, class Distance>
struct forward_iterator;
DESCRIPTION: Forward iterators can both assign and retrieve values but are
restricted in that they can move only in the forward direction. This structure
has been removed from the c++ standard.
Operators:
T forward_iterator<T, Distance>: :operator=(
const forward_iterator& x);
forward_iterator& forward_iterator<T,
Distance>: :operator++();
Returns the current value of the iterator and then increments the iterator.
B: The STL Reference 431
front_insert_iterator
TYPE: adaptor HEADER: <iteratof> [iterator.h]
namespace std{
template <class Container>
class front_insert_iterator : public iterator<
output_iterator_tag, void, void, void, void>;
DESCRIPTION: The insert iterators differ from other iterators in that they in-
sert a new object into the container when assigned a value, rather than over-
writing an existing object. This adaptor uses the push_front () method
of the underlying class to insert values at the front of a container. It is, there-
fore, restricted to use with classes that provide the push_front ( ) method.
The insert iterators are particularly useful as output iterators when used in
conjunction with the STL algorithms so that results can be written to a con-
tainer that does not have sufficient space currently allocated.
Public Members:
front_insert_iterator<Container>: : container_type;
Public Methods:
explicit front insert iterator<Container>::
front_insert_iterator(Container& x);
front_insert_iterator<Container>&
front_insert_iterator<Container>: :operator=(
typename Container: :const_reference value);
432 B: The STL Reference
Assignment operator that uses push_front () to insert a new value into the
container.
front insert_iterator<Container>&
front_insert_iterator<Container>: :operator++();
front_insert_iterator<Container>&
front_insert_iterator<Container>::operator*();
front inserter
TYPE: function HEADER: <iterator> [iterator.h]
namespace std{
template <class Container>
front_insert_iterator<Container>
front_inserter(Container& x);
generate
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
names pace std{
template <class Forwardlterator, class Generator>
void generate(FOrwardlterator first,
Forwardlterator last,
Generator gen) ;
B: The STL Reference 433
DESCRIPTION: Fills the object referenced by the iterators with the successive
values returned by gen. The range to be filled goes from first to up to
last.
generate_n
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class outputlterator, class Size,
class Generator>
void generate_n(Outputlterator first,
Size n,
Generator gen);
greater
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct greater: binary_function<T, T, bool> {
bool operator() (const T& x, const T& y) const;
} ;
}
greater_equal
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
434 B: The STL Reference
namespace std{
template <class T>
struct greater_equal: binary_functian<T, T, baal>
baal aperatar() (canst T& x, canst T& y) canst;
};
}
gslice
TYPE: class HEADER: <numeric> []
namespace std {
class gslice;
Public Methods:
size_t gslice: :start() canst;
gslice::gslice()i
gslice: :gslice(size_t start,
canst valarray<size_t>& lengths,
canst valarray<size_t>& strides) i
gslice: :gslice(canst gslice&)i
These are the gslice constructors. The first creates an empty gslice. Th(:
second is the most commonly used constructor. The final one is the copy
constructor.
Public Methods:
vaid gslice_array<T>::operator=(
canst valarray<T>& a) cansti
slice_array& gslice_array<T>: :operator=(
canst slice_array& ali
The first form assigns the subscripted members of the valarray referenced by
*this the values in a. The second form is private and allows one
slice_array to be assigned to another.
Adds the values in a onto the subscripted members of the valarray refer-
enced by *this.
Subtracts the values in a from the subscripted members of the valarray refer-
enced by *this.
gslice_array<T>: :gslice_arraY()i
gslice_array<T>: :gslice_array(const gslice_array& a)i
These are the private constructors. The first is the default constructor and the
second is the copy constructor. These might not be defined by all
implementations.
gslice_array<T>: :-gslice_arraY()i
includes
TYPE: function HEADER: <algorithm> [algo.hJ
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2>
bool includes (Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2);
indirect_array
TYPE: class HEADER: <numeric> []
names pace std {
template <class T>
class indirect_array;
Public Methods:
void indirect_array<T>: :operator=(
const valarray<T>& a) const;
indirect_array<T>& indirect_array<T>: :operator=(
const indirect_array<T>& a);
B: The STL Reference 439
The fIrst form assigns the subscripted members of the valarray referenced by
*this the values in a. The second form is private and allows one indi-
rect_array to be assigned to another.
vaid indirect_array<T>::operator*=(
canst valarray<T>& a) canst;
vaid indirect_array<T>::operator%=(
canst valarray<T>& a) canst;
Adds the values in a onto the subscripted members of the valarray refer-
enced by *this.
Subtracts the values in a from the subscripted members of the valarray refer-
enced by *this.
vaid indirect_array<T>::operator A =(
canst valarray<T>& a) canst;
void indirect_array<T>::operator«=(
canst valarray<T>& a) canst;
void indirect_array<T>::operator»=(
canst valarray<T>& a) canst;
indirect_array<T>: :indirect_array();
indirect_array<T>: : indirect_array (
canst indirect_array<T>& a);
These are the private constructors. The first is the default constructor, and the
second is the copy constructor. These might not be defined by all imp-
lementations.
indirect_array<T>: :-indirect_array();
namespace std{
template <class Inputlteratorl,
class Inputlterator2, class T>
T inner-product(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
T init);
DESCRIPTION: Computes the inner product of two ranges. The first range
goes from firstl up to last I, and the second range starts with first2
and is of the same length as the first range. The first form computes the inner
product by multiplying the ranges on an element-by-element basis, summing
the results. and adding the value ini t. The second form allows the substitu-
tion of other operations where opl replaces operator+ and op2 replaces
operator*.
inplace_merge
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallterator>
void inplace_merge(
Bidirectionallterator first,
Bidirectionallterator middle,
Bidirectionallterator last) ;
Compare comp) ;
DESCRIPTION: Merges the sequence from first up to middle with the se-
quence from middle up to last. The result is placed at first. It is as-
sumed that the input sequences are sorted. The second form allows the
specification of a comparison operator other than the default, operator<.
input_iterator
TYPE: structure HEADER: <> [iterator.h]
template <class T, class Distance>
struct input_iterator;
DESCRIPTION: Input iterators are used only to obtain values from an input
source. One of their main uses is to implement stream iterators for the read-
ing of values from streams. They move only in the forward direction and can
only be dereferenced to obtain the value associated with the iterator, never to
set it. This structure has been removed from the C++ standard.
Operators:
bool input_iterator<T, Distance>: :operator==(
const input_iterator& x);
input_iterator& input_iterator<T,
Distance>: :operator++();
Returns the current value of the iterator and then increments the iterator.
B: The STL Reference 443
Dereferences the iterator and returns the value associated with it.
inserter
TYPE: function HEADER: <iterator> [iterator.h]
namespace std{
template <class Container, class Iterator>
insert_iterator<Container> inserter(
Container& x, Iterator i);
insert iterator
TYPE: adaptor HEADER: <iterator> [iterator.h]
namespace std{
template <class Container>
class insert_iterator : public iterator<
output_iterator_tag, void, void, void, void>;
}
DESCRIPTION: The insert iterators differ from other iterators in that when a
value is assigned to them, they insert a new value into the container rather
than overwriting an existing value. An insert iterator is positioned within a
container, and all values assigned to it are inserted in front of this position.
This is particularly useful as an output iterator for one of the STL algorithms
to write output to a container that does not have a sufficient number of values
already allocated.
Public Members:
insert_iterator<Container>: : container_type;
Public Methods:
insert_iterator<Container>: :insert_iterator(
Container& x,
typename Container: :iterator i);
Constructor that initializes an insert iterator at the given position in the container.
insert_iterator<Container>&
insert_iterator<Container>: :operator=(
typename Container: :const_reference value);
insert_iterator<Container>&
insert_iterator<Container>: :operator++();
insert_iterator<Container>&
insert_iterator<Container>: :operator++(int);
insert_iterator<Container>&
insert_iterator<Container>: :operator*();
lotaGen
TYPE: class HEADER: <> [iota.h]
TIME: constant SPACE: constant
template <class T>
class IotaGen {
public:
IotaGen(const T& init);
T operator()();
};
B: The STL Reference 445
iterator
TYPE: typedef REQUIRED BY: all containers
iterator
TYPE: struct HEADER: <iterator> []
names pace std {
template<class category, class T,
class Distance = ptrdiff_t,
class Pointer = T*, class Reference T&>
struct iteratori
}
DESCRIPTION: This is a base class from which most iterators are derived.
Public Members:
iterator<Category, T>::difference_typei
iterator<Category, T>::iterator_category;
iterator<Category, T>::pointer;
446 B: The STL Reference
iterator<Category, T>::iterator_categorYi
iterator_category
TYPE: function HEADER: <> [iterator.h]
TIME: constant SPACE: constant
template <class T, class Distance>
inline input_iterator_tag iterator_category(
const input_iterator<T, Distance>&)i
inline output_iterator_tag iterator_category(const
output_iterator&)i
template <class T, class Distance>
inline torward_iterator_tag iterator_category(
const torward_iterator<T, Distance>&)i
template <class T, class Distance>
inline bidirectional_iterator_tag iterator_category(
const bidirectional_iterator<T, Distance>&)i
DESCRIPTION: the iterator tags are a series of empty structures used to iden-
tify the category to which an iterator belongs. The expression iterator-
_traits<iter>: : iterator_category returns an iterator tag, indi-
cating the category to which iter belongs. No values are contained within
the iterator tags since their type alone is sufficient to identify the iterator
category.
iterator traits
TYPE: struct HEADER: <iterator> []
namespace std{
template<class T>
struct iterator_traits;
Public Members:
iterator_traits::difference_typei
A type which can be used to represent the difference between two iterators.
iterator_traits: :iterator_categorYi
One of the iterator tags that determines the category of the iterator.
iterator_traits: :pointeri
iterator_traits: :referencei
iterator_traits: :value_typei
void iter_sWap(FOrwardlteratorl a,
Forwardlterator2 b);
less
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct less : binary_function<T, T, bool> {
bool operator() (const T& x, const T& y) const;
};
}
lexicographical_compare
TYPE: function HEADER: <algorithm> [algobase.hJ
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2>
bool lexicographical_compare(
Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2) ;
template <class Inputlteratorl,
class Inputlterator2, class Compare>
bool lexicographical_compare (
Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
Compare comp) ;
)
B: The STL Reference 451
list
TYPE: class HEADER: <list> [list.h]
names pace std {
template <class T, class Allocator allocator<T> >
class list;
Public Members:
list<T, Allocator>: : allocator_type;
list<T, Allocator>::iterator;
Public Methods:
void list<T, Allocator>::assign(
size_type n,
const T& value);
template<class Inputlterator>
void list<T, Allocator>::assign(
Inputlterator first,
Inputlterator last);
These methods erase the content of the list and replace it with new values speci-
fied by the parameters. The frrst form fills the list with n copies of value.
The second form inserts the values from first up to last.
B: The STL Reference 453
Returns an iterator that is one past the last element in the list.
Deletes the element at the indicated position from the list. The second form de-
letes all elements in the range from first up to last. An iterator refer-
encing the element after the last element erased is returned. Only iterators,
pointers, and references to the erased elements are invalidated.
Inserts the element x just before pos i tion. The first form returns an iterator
that references the element just inserted. The second form inserts the ele-
ments from first up to last just before position. The third form in-
serts n copies of the element x just before po sit ion.
The first form constructs an empty list. The second form constructs a list con-
taining n copies of value. The third form constructs a list containing the
elements from first up to last. The fourth form is the copy constructor.
Returns the maximum number of elements that can be stored in the list.
B: The STL Reference 455
Merges the sorted list x into the current list, which is assumed to be sorted. Af-
ter the merge, the list x has no elements, and its length is reduced to zero.
The merge operation does not remove duplicates. The second form uses
comp to compare elements rather than opera tor<.
Returns an iterator that is one past the end of the list and can be used as the start-
ing value for a reverse iterator.
template<class Predicate>
void list<T, Allocator>::remove_if(Predicate p)i
Returns an iterator that can be used to find the end for a reverse iteration.
Changes the size of the list so that it contains size members. If the list has less
than size elements, new elements are appended to make it the desired
length, with the new elements assigned value. If the list has more than
s i z e elements, elements are removed from the end to make it the desired
length. If the list is the desired length, nothing is done.
Sorts the elements of the list into ascending order. This is an order n log n sort.
The second form uses comp to compare elements rather than opera tor<.
list<T, Allocator>& x,
iterator first,
iterator last) ;
Inserts the entire list x into the list so that it is placed immediately before posi-
tion and removes the inserted elements from x. The second form inserts
the element from the list x referenced by i just before position. It also
removes the element referenced by i from the list x. The third form places
the elements from first up to last from the list x just before pos i tion
and removes them from the list x.
Swaps the contents of the object list with the list x so that each has the contents
of the other.
Removes adjacent duplicates from a sorted list so that only a single copy of each
element remains. The second form uses p to determine equivalence.
Nonmember Functions:
template <class T, Allocator>
bool operator==(const list<T, Allocator>& x,
const list<T, Allocator>& y);
logical_not
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct logical_not : binary_function<T, T, bool> {
bool operator() (const T& x) const;
} ;
}
lower bound
TYPE: function HEADER: <algorithm> [algo.h]
TIME: logarithmic SPACE: constant
namespace std{
template <class Forwardlterator, class T>
Forwardlterator lower_bound(
460 B: The STL Reference
Forwardlterator first,
Forwardlterator last,
const T& value) ;
make_heap
TYPE: function TYPE: <algorithm> [heap.h]
TIME: linear SPACE: constant
namespace std{
template <class RandomAccesslterator>
void make_heap(RandomAccesslterator first,
RandomAccesslterator last) ;
map
TYPE: class HEADER: <map> [map.h]
namespace std {
template <class Key, class T,
class Compare = less<Key>,
class Allocator = allocator<pair<const Key, T> > >
class map;
}
DESCRIPTION: The map stores and retrieves objects based on a key provided
when the object is inserted. The key can be of any type, and it does not have
to be contained within the object. The keys for the map must be unique, but
the multimap supports duplicate keys. The map allows you to insert objects
and provides fast retrieval using a comparison function to determine the
equality of objects. The objects are actually stored in a tree so that insertion
and retrieval take logarithmic time. The map is useful for applications that
implement the concept of associative arrays. Internally, the objects in the
map are always ordered so that an iterator will retrieve the objects in sorted
order.
Public Members:
map<Key, T, Compare, Allocator>: : allocator_type;
map<Key, T, Compare,
Allocator>: :const_reverse_iterator;
462 B: The STL Reference
Public Methods:
iterator map<Key, T, Compare, Allocator>: :begin();
const_iterator map<Key, T, Compare, Allocator>::
begin() const;
Returns a count of the number of objects equal to x. For a map this will be zero
or one.
The first form deletes the object referenced by the iterator posi tion. The sec-
ond form deletes all occurrences of the object x and returns the number of
objects deleted. The third form deletes all the objects in the range from
first up to last.
pair<iterator, iterator>
map<Key, T, Compare, Allocator>::
equal_range(const key_type& x);
pair<const_iterator, const_iterator>
map<Key, T, Compare, Allocator>::
equal_range(const key_type& x) const;
Finds the first object equal to x and returns an iterator referencing it. If not
found, it returns a past-the-end iterator.
Inserts a new value into the map. The first form returns a pair whose second
member is a bool indicating if the insertion was successful. An insertion
can fail if the object being inserted is already in the map. The first member is
an iterator referencing the newly inserted object or the existing object if the
object being inserted is a duplicate. The second form inserts x into the con-
tainer if it is not already present. It returns an iterator referencing the ele-
ment equal to x in the map. posn is a hint as to where the search for the
insertion position should begin. The third form inserts the elements from
first up to last, providing each element is not already in the map.
These are the map constructors. The first two allow the specification of an alter-
nate comparison function. The first form constructs an empty map and the
second constructs a map and inserts the values from the range of the intera-
tors first up to last. The third form is the copy constructor.
Returns a reference to the object in the container that matches the given key.
Nonmember Functions:
template <class Key, class Compare, class Allocator>
bool operator==(
const map<Key, T, Compare, Allocator>& x,
const map<Key, T, Compare, Allocator>& y);
Public Methods:
vaid mask_array<T>: :operator=(
canst valarray<T>& a) canst;
mask_array& mask_array<T>: :operator=(
canst mask_array& a);
The first form assigns the subscripted members of the valarray referenced by
*this the values in a. The second form is private and allows one
mask_array to be assigned to another.
Adds the values in a onto the subscripted members of the valarray refer-
enced by *this.
Subtracts the values in a from the subscripted members of the valarray refer-
enced by *this.
mask_array<T>: :mask_array();
mask_array<T>: : mask_array (
const mask_array<T>& a);
These are the private constructors. The first is the default constructor, and the
second is the copy constructor. These might not be defined by all
implementations.
mask_array<T>: :-mask_array();
max
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
const T& max(const T& a,
constT& b);
mem fun
TYPE: function HEADER: <functional> [function.h]
namespace std{
template<class S, class T>
mem_fun_t<S,T>
mem_fun(S (T: :*mth)(»;
}
mem fun1
TYPE: function HEADER: <functional> [function.h]
472 B: The STL Reference
namespace std{
template<class S, class T, class A>
mem_funl_t<S,T,A>
mem_funl ( S ( T: : * f) (A) ) ;
Public Members:
S operator()(T& r)i
Public Members:
S operator()(T& r, A X)i
This invokes the adapted function as (r. *mth) (x), where r is a reference to
the object on which to invoke the method. This should be used with contain-
ers of objects rather than containers of object pointers.
mem- fun- t
TYPE: adaptor HEADER: <functional> [function.h]
namespace std{
template <class S, class T>
class mem_fun_t
: public unary_function<T*, S>i
Public Methods:
explicit mem_fun_t(S (T: :*mth)(»i
S operator()(T* p)i
This invokes the adapted method as (p - > *mth) ( ) so that p is a pointer to the
object on which the method should be invoked. Since a pointer to an object
is required, this is suitable only for containers of object pointers.
B: The STL Reference 475
mem- fun1 - t
TYPE: adaptor HEADER: <functional> [function.h]
namespace std(
template <class S, class T, class A>
class mem_funl_t
: public binary_function<T*, A, S>;
Public Methods:
explicit mem_funl_t(S (T: :*mth)(A»;
S operator()(T* p, A x);
merge
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
narnespace std(
template <class Inputlteratorl,
class Inputlterator2, class outputlterator>
outputlterator merge(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
outputlterator result);
476 B: The STL Reference
min
TYPE: function HEADER: <algorithm> [algobase.hJ
TIME: constant SPACE: constant
namespace std{
template <class T>
const T& min(const T& a,
const T& b);
DESCRIPTION: Returns the minimum of the values a and b. The second form
allows the specification of a comparison other than the default opera tor<.
min element
TYPE: function HEADER: <algorithm> [algo.hJ
. TIME: linear SPACE: constant
namespace std(
template <class Forwardlterator>
Inputlterator min_element(Forwardlterator first,
B: The STL Reference 477
Forwardlterator last) ;
template <class Forwardlterator, class Compare>
Inputlterator min_element(Forwardlterator first,
Forwardlterator last,
Compare comp);
}
minus
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct minus: binary_function<T, T, T> {
T operator() (const T& x, const T& y) const;
};
mismatch
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2>
pair<Inputlteratorl, Inputlterator2>
mismatch(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2);
pair<Inputlteratorl, Inputlterator2>
mismatch(Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
BinaryPredicate binary_pred);
modulus
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct modulus: binary_function<T, T, T> {
T operator() (const T& x, const T& y) const;
};
multimap
TYPE: class HEADER: <map> [multimap.h]
names pace std {
template <class Key, class T,
class Compare = less<Key>,
B: The STL Reference 479
DESCRIPTION: The multimap stores and retrieves objects based on a key pro-
vided when the object is inserted. The key can be of any type, and it does
not have to be contained within the object. The multimap supports duplicate
keys. The mUltimap allows you to insert objects and provides fast retrieval
using a comparison function to determine the equality of objects. The ob-
jects are actually stored in a tree so that insertion and retrieval take logarith-
mic time. The multimap is useful for applications that want to implement the
concept of associative arrays. Internally, the objects in the multimap are al-
ways ordered so that an iterator will retrieve the objects in sorted order.
Public Members:
multimap<Key, T, Compare, Allocator>: : allocator_type;
multimap<Key, T, Compare,
Allocator>: :const_reverse_iterator;
Public Methods:
iterator multimap<Key, T, Compare, Allocator>::
begin ( );
B: The STL Reference 481
The first form deletes the object referenced by the iterator pos i tion. The sec-
ond form deletes all occurrences of the object x and returns the number of
objects deleted. The third form deletes all the objects in the range from
first up to last.
pair<iterator, iterator>
multimap<Key, T, Compare, Allocator>: : equal_range (
const key_type& x);
pair<const_iterator, const_iterator>
482 B: The STL Reference
Finds the first object equal to x and returns an iterator referencing it. If not
found, it returns a past-the-end iterator.
The first form inserts a new value into the mUltimap. It returns an iterator refer-
encing the newly inserted element. The second form inserts the series of val-
ues from fir s t up to 1 a s t. The third form inserts the value x into the
multimap. The iterator pos i tion is used as a hint about where to start
searching for the insertion position. It returns an iterator referencing the
newly inserted element.
These are the multimap constructors. Two of these allow the specification of an
alternate comparison function other than the one specified in the template pa-
rameters. The first form constructs an empty muItimap, and the second con-
structs a multimap and inserts the values from the range of the iterators
first up to last. The third form is the copy constructor.
Returns a reference to the object in the container that matches the given key.
const_reverse_iterator
multimap<Key, T, Compare, Allocator>::rbegin() const;
const reverse_iterator
multimap<Key, T, Compare, Allocator>::rend() const;
Nonmember Functions:
template <class Key, class Compare, class Allocator>
bool operator==(
const multimap<Key, T, Compare, Allocator>& x,
const multimap<Key, T, Compare, Allocator>& y);
multiplies
TYPE: function object HEADER: <functional> [ ]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct multiplies: binary_function<T, T, T> {
T operator() (const T& x, const T& y)i
486 B: The STL Reference
} ;
}
multiset
TYPE: class HEADER: <set> [multiset.h]
namespace std {
template <class Key, class Compare less<Key>,
class Allocator = allocator<Key> >
class multiset;
}
Public Members:
multiset<Key, Compare, Allocator>: : allocator_type;
Public Methods:
iterator multiset<Key, Compare, Allocator>: :begin();
const_iterator multiset<Key, Compare, Allocator>::
begin ( ) const;
The first form deletes the object referenced by the iterator pos i tion. The sec-
ond deletes all occurrences of the object x and returns the number of objects
deleted. Finally, the third form deletes all the objects in the range from
first up to last.
pair<iterator, iterator>
multiset<Key, Compare, Allocator>: : equal_range (
const key_type& x) const;
Finds the first object equal to x and returns an iterator referencing it. If not
found, it returns a past-the-end iterator.
The first form inserts a new value into the multiset and returns an iterator that
references the newly inserted object. The second form inserts a new value,
using posn as a hint about where to start searching for the insertion position.
The third form inserts the series of values from first up to last.
These are the multiset constructors. Two of these allow the specification of an
alternate comparison function other than the one specified in the template pa-
rameters. The first form constructs an empty multiset, and the second con-
structs a multiset and inserts the values from the range of the iterators first
up to last. The third form is the copy constructor.
const_reverse_iterator
multiset<Key, Compare, Allocator>:: rend() const;
Nonmember Functions:
template <class Key, class Compare, Allocator>
bool operator==(
const multiset<Key, Compare, Allocator>& x,
const multiset<Key, Compare, Allocator>& y);
Returns true if the multiset x is lexicographically less than the multi set y.
Returns true if the two sets are not equal on an element-by-element basis.
Returns true if the multiset x is lexicographically less than or equal to the mul-
tiset y.
negate
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct negate: unary_function<T, T>
T operator() (const T& x) const;
B: The STL Reference 493
};
next_permutation
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallterator>
bool next-permutation(
Bidirectionallterator first,
Bidirectionallterator last);
not1
TYPE: function HEADER: <functional> [function.h]
namespace std{
template <class Predicate>
unary_negate<Predicate>
notl(const Predicate& pred);
}
not2
TYPE: function HEADER: <functional> [function.h]
namespace std{
template <class Predicate>
binary_negate<Predicate>
not2(const Predicate& pred);
nth_element
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class RandomAccesslterator>
void nth_element(
RandomAccesslterator first,
RandomAccesslterator nth,
RandomAccesslterator last);
template <class RandomAccesslterator,
class Compare>
void nth_element(
RandomAccesslterator first,
RandomAccesslterator nth,
RandomAccesslterator last,
Compare comp) ;
DESCRIPTION: Places the nth element in the range from first up to last
in the position it would lie if the range had been sorted. It also ensures that
all of the elements prior to the final location of the nth element are less than
or equal to all of the elements that are after the final position of the nth ele-
ment. It is assumed that nth is an iterator that lies within the range from
first up to last. The second form allows an alternate comparison object
other than the default, operator<.
operator>
TYPE: operator HEADER: <utility> [function.h]
TIME: constant SPACE: constant
namespace std{
namespace rel_ops {
template <class T>
496 B: The STL Reference
operator>=
TYPE: operator HEADER: <utility> [function.h]
TIME: constant SPACE: constant
namespace std{
namespace rel_ops {
template <class T>
bool operator>=(const T& x, const T& y);
}
}
operator<=
TYPE: operator HEADER: <utility> [function.h]
TIME: constant SPACE: constant
namespace std{
names pace rel_ops {
template <class T>
bool operator<=(const T& x, const T& y);
}
operator! =
TYPE: operator HEADER: <utility> [function.h]
TIME: constant SPACE: constant
namespace std{
namespace rel_ops {
template <class T>
bool operator!=(const T& x, const T& y);
B: The STL Reference 497
output_iterator
TYPE: structure HEADER: <> [iterator.h]
struct output_iteratori
DESCRIPTION: Output iterators can only output values to a destination and
are used primarily to implement output stream iterators. The operations are
restricted so that values can be assigned to output iterators but not retrieved
from them. This has been removed from the C++ standard as a class.
Operators:
boo 1 output_iterator<T, Distance>: :operator==(
const output_iterator& X)i
output_iterator& output_iterator<T,
Distance>: :operator++()i
Rncrements the iterator and returns a reference to the new value of the iterator.
Returns the current value of the iterator and then increments the iterator.
T output_iterator<T, Distance>::operator*()i
pair
TYPE: struct HEADER: <utility> [pair.h]
namespace std {
template <class TI, class T2>
struct pair;
498 B: The STL Reference
DESCRIPTION: While this can be used to represent a pair of any two objects,
it is commonly used to represent a pair of iterators that indicate the beginning
and ending of a sequence. The header file also defines templates for op-
era tor== and opera tor< for use with pairs.
Public Members:
pair<Tl, T2>: : first_type;
Public Methods:
pair<Tl, T2>: :pair();
pair<TI, T2>: :pair(const TI& x, const T2& y);
template<class U, class v>
pair<TI, T2>: :pair(const pair<U, v> &p);
The first form is the default constructor that uses T I () and T 2 () to construct
first and second, respectively. The second form is the most commonly
used constructor and assigns the values x and y to first and second, re-
spectively. The third form accepts a pair with different template parameters
and uses implicit conversions to obtain the correct types.
Nonmember Functions:
template <class TI, class T2>
pair<Tl, T2> make-pair(const Tl& x,
const T2& y);
Returns a Boolean indicating if the pair x is less than the pair y. This returns the
result of the expression
namespace std(
template <class Inputlterator,
class RandomAccesslterator>
RandomAccesslterator partial_sort_copy(
Inputlterator first,
Inputlterator last,
RandomAccesslterator result_first,
RandomAccesslterator result_last);
partition
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallterator,
class Predicate>
Bidirectionallterator partition(
Bidirectionallterator first,
Bidirectionallterator last,
Predicate pred) ;
plus
TYPE: function object HEADER: <functional> [function.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
struct plus : binary_functian<T, T, T> {
T aperatar() (canst T& x, canst T& y) canst;
};
}
pointer
TYPE: typedef REQUIRED BY: all containers
Public Methods:
explicit pointer_to_hinary_function(
Result (*fun) (Argl, Arg2»;
opera tor ( ) is invoked to call the function being adapted. The adapted func-
tion is passed the arguments x and y.
Public Methods:
explicit pointer_to_unary_function(
Result (*fun)(Arg»;
opera tor ( ) is invoked to call the function being adapted. The adapted func-
tion is passed the argument x.
504 B: The STL Reference
prey_permutation
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallterator>
bool prev-permutation(
Bidirectionallterator first,
Bidirectionallterator last) ;
template <class Bidirectionallterator,
class Compare>
bool prev-permutation(
Bidirectionallterator f:Lrst,
Bidirectionallterator last,
Compare comp) ;
}
B: The STL Reference 505
namespace std{
template <class RandomAccesslterator>
void push_heap(RandOmAccesslterator first,
RandomAccesslterator last) ;
priority_queue
TYPE: adaptor HEADER: <queue> [stack.h]
namespace std {
template <class T, class Container = vector<T>,
class Compare =
less<typename Container: : value_type> >
class priority_queue;
}
Public Members:
priority_queue<T, Container, Compare>: : container_type;
The type of the container from which the priority queue was constructed.
A type suitable for representing the difference between any two iterators.
Public Methods:
bool priority_queue<T, Container, Compare>: :empty()
const;
Removes the object at the front of the priority queue but does not return the
value.
Constructors that allow you to change the comparison function for specific in-
stances of the class. The first form creates an empty priority queue, and the
second fills the new priority queue with objects stored in another container
from the iterator first up to last.
508 B: The STL Reference
const value_type&
priority_queue<T, container, Compare>::top() const;
Returns the value at the top of the priority queue without removing it from the
queue.
queue
TYPE: adaptor HEADER: <queue> [stack.h]
names pace std {
template <class T, class Container = deque<T> >
class queue;
}
Public Members:
priority_queue<T, Container, Compare>: : container_type;
B: The STL Reference 509
The type of the container from which the priority queue was constructed.
A type suitable for representing the difference between any two iterators.
Public Methods:
value_type& queue<T, Container>: :back();
const value_type& queue<T, Container>: :back() const;
These methods return a reference to the object at the back end of the queue.
These methods return a reference to the object at the front of the queue.
This removes the object at the head of the queue, but does not return its value.
Nonmember Functions:
template <class T, class Container>
bool operator==(const queue<T, Container>& x,
const queue<T, Container>& y) ;
Compares two queues for equality by invoking opera tor== ( ) on the underly-
ing container.
Returns true if the queue x is less than the queue y as determined by invoking
opera tor< ( ) on the underlying container.
Operators:
T random_access_iterator<T, Distance>: :operator=(
const random_access_iterator& x);
Returns true if the first iterator is less than or equal to the second.
Returns true if the first iterator is greater than or equal to the second.
random_access iterator&
random_access_iterator<T, Distance>: :operator+(
random_access_iterator&)i
Adds two iterators together and returns an iterator representing their sum.
random_access iterator&
random_access_iterator<T, Distance>::operator++()i
random_access iterator&
random_access_iterator<T, Distance>: :operator++(int)i
Returns the current value of the iterator and then increments the iterator.
random_access_iterator&
random_access_iterator<T, Distance>: :operator+=(int)i
Adds an arbitrary integer to the iterator to move it that number of positions in the
forward direction. Negative values move the iterator backwards.
random_access_iterator&
random_access_iterator<T, Distance>: :operator-(
random_access_iterator&)i
Subtracts two iterators from each other and returns an iterator representing their
difference.
random_access_iterator&
random_access_iterator<T, Distance>: :operator-=(int)i
Subtracts an arbitrary integer from the iterator to move it that number of posi-
tions in the reverse direction.
random_access_iterator&
random_access_iterator<T, Distance>: :operator--()i
B: The STL Reference 513
Returns the current value of the iterator and then decrements the iterator.
T& random_access_iterator<T,
Distance>: :operator[] (int)i
Returns a reference to the object in the position of the container indicated by the
offset from the iterator.
random shuffle
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class RandomAccesslterator>
void random_shuffle(
RandomAccesslterator first,
RandomAccesslterator last) i
raw_storage_iterator
TYPE: adaptor HEADER: <memory> [iterator.h]
namespace std {
template <class Outputlterator, class T>
class raw_storage_iterator
: public iterator<output_iterator_tag,
void, void, void, void>;
Public Members:
raw_storage_iterator<Outputlterator, T>&
raw_storage_iterator<Outputlterator, T>::
operator=(const T& element);
raw_storage_iterator<Outputlterator, T>&
raw_storage_iterator<Outputlterator, T>: :operator++();
Increments the iterator to move it forward one position and returns its new value.
raw_storage_iterator<Outputlterator, T>
raw_storage_iterator<Outputlterator, T>
: :operator++(int);
Returns the current value of the iterator and then increments its value to move it
in the forward direction.
raw_storage_iterator<Outputlterator, T>&
raw_storage_iterator<Outputlterator, T>: :operator*();
B: The STL Reference 515
Dereference operator that returns a reference to the raw storage iterator to which
it is applied.
reference
TYPE: typedef REQUIRED BY: all containers
remove
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlterator, class T>
Forwardlterator remove(Forwardlterator first,
Forwardlterator last,
const TO. value) ;
DESCRIPTION: Copies all elements from first up to last that are not
equal to value to the location referenced by resul t. The function returns
an iterator that is one past the end of the result sequence. The original se-
quence is left unchanged. No assumption is made about the order of ele-
ments in the original sequence.
remove if
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
B: The STL Reference 51.1
namespace std{
template <class Forwardlterator, class Predicate>
Forwardlterator remove_if(Forwardlterator first,
Forwardlterator last,
Predicate pred) ;
}
replace
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlterator, class T>
void replace(FOrwardlterator first,
Forwardlterator last,
const T& old_value,
const T& new_value);
}
const T&
Forwardlterator last,
Predicate pred,
const T& new_value) ;
}
DESCRIPTION: Replaces all values in the range from first up to last that
satisfy the predicate by new_value.
reverse
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Bidirectionallterator>
void reverse(Bidirectionallterator first,
Bidirectionallterator last);
}
DESCRIPTION: Reverses the order of the elements in the range from first
up to last.
reverse iterator
TYPE: adaptor HEADER: <iterator> [iterator.h]
namespace std
template <class Iterator>
class reverse_iterator : public
iterator<typename iterator_traits<Iterator>::
iterator_category,
iterator<typename iterator_traits<Iterator>::
value_type,
iterator<typename iterator_traits<Iterator>::
iterator_category,
iterator<typename iterator_traits<Iterator>::
iterator_category,
iterator<typename iterator_traits<Iterator>::
iterator_category>;
Public Members:
reverse_iterator<Iterator>: : difference_type;
A type that can represent the difference between any two iterators.
reverse_iterator<Iterator>: :iterator_type;
reverse_iterator<Iterator>: : pointer;
reverse_iterator<Iterator>: : reference;
Public Methods:
reverse_iterator&
reverse_iterator<Iterator>: :operator++()i
reverse_iterator
reverse_iterator<Iterator>: :operator++(int)i
reverse_iterator&
reverse_iterator<Iterator>: :operator--()i
reverse_iterator
reverse_iterator<Iterator>: :operator--(int)i
reverse_iterator
reverse_iterator<Iterator>: :operator+(
difference_type n) consti
Adds n onto the value of the iterator and returns this. The value of the iterator
itself is not changed.
reverse_iterator&
reverse_iterator<Iterator>: :operator+=(
difference_type n)i
reverse_iterator
reverse_iterator<Iterator>: :operator-(
difference_type n) consti
reverse_iterator&
reverse_iterator<Iterator>: :operator-=(
difference_type n)i
Returns a reference to the member n positions from the position of the iterator.
reverse_iterator<Iterator>: :reverse_iterator();
explicit reverse_iterator<Iterator>::reverse_iterator(
Iterator x);
template<class U>
reverse_iterator<Iterator>: :reverse_iterator(
const reverse_iterator<U>& u);
Nonmember Functions:
template <class Iterator>
bool operator==(
const reverse_iterator<Iterator>& x,
const reverse_iterator<Iterator>& y);
rotate
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlterator>
524 B: The STL Reference
search
TYPE: function HEADER: <algorithm> [algo.h]
TIME: quadratic SPACE: constant
namespace std{
template <class Forwardlteratorl,
class Forwardlterator2>
Forwardlteratorl search (
B: The STL Reference 525
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2,
Forwardlterator2 last2)i
set
TYPE: class HEADER: <set> [set.h]
526 B: The STL Reference
namespace std [
template <class Key, class Compare less<Key>,
class Allocator = allocator<Key> >
class set;
Public Members:
set<Key, Compare, Allocator>: : allocator_type;
set<Key, Compare,
Allocator>: :const_reverse_iterator;
Public Methods:
iterator set<Key, Compare, Allocator>: :begin()
const_iterator set<Key, Compare, Allocator>: :begin()
const;
528 B: The STL Reference
Returns a count of the number of objects equal to x. For a set, this will be 0 or
1.
The first form deletes the object referenced by the iterator posi tion. The sec-
ond form deletes all occurrences of the object x and returns the number of
objects deleted. The third form deletes all the objects in the range from
first up to last.
pair<iterator, iterator>
set<Key, Compare, Allocator>: :equal_ranqe(
const key_type& x) const;
Finds the first object equal to x and returns an iterator referencing it. If not
found, it returns a past-the-end iterator.
Inserts a new value into the set. The first form returns a pair whose second
member is a bool indicating if the insertion was successful. An insertion
can fail if the object being inserted is already in the set. The first member is
an iterator referencing the newly inserted object or the existing object if the
object being inserted is a duplicate. The second form does the same, using
posn as a hint about where to start searching for the insertion position, and
returning an iterator referencing the element in the set equal to value. The
third form inserts the values from first up to last, provided they are not
already in the set.
These are the set constructors. Two of these allow the specification of an alter-
nate comparison function other than the one specified in the template pa-
rameters. The first form constructs an empty set, and the second constructs a
set and inserts the values from the range of the interators first up to
last. The third form is the copy constructor.
Nonmember Functions:
template <class Key, class Compare, class Allocator>
bool operator==(
const set<Key, Compare, Allocator>& x,
const set<Key, Compare, Allocator>& y)i
set difference
TYPE: function HEADER: <algorithm> [aJgo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2, class Outputlterator>
Outputlterator set_difference (
Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
Outputlterator result);
Compare comp) ;
}
DESCRIPTION: Both the first sequence from firstl up to lastl and the
second sequence from first2 up to last2 are sets if they are in ascending
order. The function computes the set difference of the two sets and places
the resultant sequence at the position referenced by result. An iterator is
returned that is one past the end of the resultant sequence. The second form
of the function allows the specification of a comparison operator other than
the default, opera tor<.
set intersection
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2, class Outputlterator>
Outputlterator set_intersection(
Inp'utlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
Outputlterator result) ;
DESCRIPTION: Both the first sequence from firstl up to lastl and the
second sequence from first2 up to last2 are sets if they are in ascending
order. The function computes the intersection of the two sets and places the
resultant sequence at the location referred to by result. The function re-
turns an iterator that is one past the end of the resultant sequence. The sec-
ond form of the function allows the specification of a comparison operator
other than the default, opera tor<.
DESCRIPTION: Both the first sequence from firstl up to lastl and the
second sequence from first2 up to last2 are sets if they are in ascending
order. The function computes the symmetric set difference of the two sets
and places the resultant sequence at the location referred to by result.
The function returns an iterator that is one past the end of the resultant se-
quence. The second form of the function allows the specification of a com-
parison operator other than the default, operator<.
B: The STL Reference 535
set union
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Inputlteratorl,
class Inputlterator2, class outputlterator>
outputlterator set_union (
Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
Inputlterator2 last2,
outputlterator result);
DESCRIPTION: Both the first sequence from firstl up to lastl and the
second sequence from first2 up to last2 are sets if they are in ascending
order. The function computes the set union of the two sets and places the re-
sultant sequence at the location referred to by resul t. The function returns
an iterator that is one past the end of the resultant sequence. The second
form of the function allows the specification of a comparison operator other
than the default, operator<.
slice
TYPE: class HEADER: <numeric> []
names pace std {
class slice;
Public Functions:
size_t slice: :size() canst;
slice: : slice () ;
slice: :slice(size_t start,
size_t length,
size_t stride) ;
slice: :slice(canst slice&);
These are the slice constructors. The default constructor creates an empty
slice and is used only to construct arrays of slices. The second form is
the most commonly used and takes a starting index, the number of indices to
generate, and a stride that is repeatedly added to the starting index to gener-
ate the subsequent indices. The third form is the copy constructor.
Returns the stride that is the increment added to the starting index to generate
subsequent indices.
B: The STL Reference 537
Public Members:
typedef T slice_array<T>: : value_type;
Public Methods:
vaid slice_array<T>: : operator= (
canst valarray<T>& a) canst;
slice_array& slice_array<T>: :operator=(
canst slice_array& a);
The first form assigns the subscripted members of the valarray referenced by
*this the values in a. The second form is private and allows one
slice_array to be assigned to another.
void slice_array<T>::operator+=(
const valarray<T>& a) const;
Adds the values in a onto the sUbscripted members of the valarray refer-
enced by *this.
Subtracts the values in a from the subscripted members of the valarray refer-
enced by *this.
void slice_array<T>::operator&=(
const valarray<T>& a) const;
void slice_array<T>::operatorl=(
const valarray<T>& a) const;
void slice_array<T>::operator«=(
const valarray<T>& a) const;
slice_array<T>: :slice_arraY{)i
slice_array<T>: : slice_array {const slice_arraY&)i
These are the private constructors. The first is the default constructor and the
second is the copy constructor. These might not be defined by all
implementations.
slice_array<T>: :-slice_arraY{)i
slist
TYPE: class HEADER: <> [) {slist.h}
narnespace std{
ternplate<class T, class Allocator allocator<T> >
class slisti
Public Members:
slist<T, Allocator>: :const_iterator;
Public Methods:
iterator slist<T, Allocator>: :begin();
const_iterator slist<T>: :begin();
The first form deletes the element referenced by it, while the second form de-
letes the elements from first up to last.
The first form erases the element immediately after it. The second form erases
all elements in the range [first + 1, last).
The first form inserts the value t immediately before the element referenced by
it. It returns an iterator referencing the element inserted. The second form
inserts the elements from first up to last immediately before the ele-
ment referenced by it. The third form inserts n copies of the value t imme-
diately before the element referenced by it.
The first form inserts T () immediately after it. The second inserts t immedi-
ately after it. Both of these forms return an iterator referencing the element
inserted. The third form inserts the elements from first up to last im-
mediately after it. The fourth form inserts n copies of t immediately after it.
Performs a merge of the two sorted lists, *this and 1, removing the elements
from 1 as they are inserted in *this. *this and 1 must be separate lists.
The second form uses a comparison function other than the default,
operator<.
Removes all elements equal to valor that satisfy the predicate pred.
These are the class constructors. The first constructor creates an empty list. The
second creates a list with n members, each initialized with T ( ). The third
also creates a list of length n, but each node is a copy of t. The fourth is the
copy constructor. The final constructor creates a list whose contents are a
copy of the elements from first up to last.
Performs a stable sort on the list using opera tor<. The second form uses a
comparison function other than the default, opera tor<.
Inserts the elements of the list 1 immediately before the element referenced by
it and removes the inserted elements from the list 1. It is required that
*this and 1 be different lists. The first form moves all elements of 1. The
second form moves only the element of 1 referenced by i. The third form
moves the elements of 1 from first up to last.
The first form inserts the element immediately after p so that it is immediately
after it. The element immediately after p is removed from the container
holding it. The second form moves the elements in the range from first
+ 1 up to last + 1 immediately after it.
Removes adjacent duplicate elements so that only a single copy of each element
remains. The second form uses a comparison function other than the default,
operator==.
Nonmember Functions:
bool operator==(
const slist<T, Allocator>& 11,
const slist<T, Allocator>& 12);
bool operator«
const slist<T, Allocator>& 11,
const slist<T, Allocator>& 12);
sort
TYPE: function HEADER: <algorithm> [algo.h]
TIME: n logn SPACE: logarithmic
namespace std{
template <class RandomAccesslterator>
void sort(RandomAccesslterator first,
RandomAccesslterator last) ;
DESCRIPTION: Sorts the sequence from first up to last. The first form
of the function uses opera tor< to perform comparisons, while the second
form uses the function object comp to perform the comparisons.
546 B: The STL Reference
sort_heap
TYPE: function HEADER: <algorithm> [heap.h]
TIME: n logn SPACE: constant
namespace std(
template <class RandomAccesslterator>
void sort_heap(RandomAccesslterator first,
RandomAccesslterator last) ;
stable_partition
TYPE: function HEADER: <algorithm> [algo.h]
TIME: n log n SPACE: constant
namespace std(
template <class Bidirectionallterator,
class Predicate>
Bidirectionallterator
stable-partition(Bidirectionallterator first,
Bidirectionallterator last,
Predicate pred) ;
}
B: The STL Reference 547
stable sort
TYPE: function HEADER: <algorithm> [algo.h]
TIME: n log2 n SPACE: logarithmic
namespace std{
template <class RandornAccesslterator>
void stable_sort (
RandornAccesslterator first,
RandornAccesslterator last) ;
DESCRIPTION: Sorts the sequence from first up to last so that the rela-
tive order of equal elements is preserved. The first form of the function uses
operator< to perform comparisons and results in a sequence in ascending
order. The second form uses the function object comp to perform the
comparison.
stack
TYPE: adaptor HEADER: <stack> [stack.h]
namespace std{
template <class T, class Container = deque<T> >
548 B: The STL Reference
class stack;
DESCRIPTION: The stack is a container adaptor that can use one of the se-
quence containers (vector, deque, list) to create a new container with a re-
stricted set of operations. Stacks are characterized by allowing addition and
deletion of elements at one end of the stack only. This means that the items
on a stack emerge in the opposite of the order in which they were inserted.
This capability is useful for a wide variety of applications. The class makes
extensive use of the push_back () and pop_back () operations of the
underlying class. Since all sequence containers implement these operations
in constant time, the decision about which container the stack should be
based on comes down to a question of efficiency of memory utilization.
Public Members:
stack<T, Container>: : container_type;
Public Methods:
bool stack<T, Container>: :empty() const;
Removes the value from the top of the stack but does not return the value.
Default constructor.
Returns the value at the top of the stack without removing it from the stack.
Nonmember Functions:
template <class T, class Container>
bool operator==(const stack<T, Container>& x,
const stack<T, Container>& y);
Compares two stacks by invoking operator< as defined for the class on which
the stack is based.
Returns true if the first stack is less than or equal to the second.
550 B: The STL Reference
Returns true if the first stack is greater than or equal to the second.
swap
TYPE: function HEADER: <algorithm> [algobase.h]
TIME: constant SPACE: constant
namespace std{
template <class T>
void swap(T& a,
T& b);
}
swap_ranges
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlteratorl,
class Forwardlterator2>
Forwardlterator2 swap_ranges (
Forwardlteratorl firstl,
Forwardlteratorl lastl,
Forwardlterator2 first2);
times
TYPE: function object HEADER: <> [function.h]
TIME: constant SPACE: constant
template <class T>
struct times: binary_function<T, T, T> {
T operator() (const T& x, const T& y)i
} i
transform
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
names pace std{
template <class Inputlterator,
class Outputlterator, class UnaryOperation>
Outputlterator transform (
Inputlterator first,
Inputlterator last,
Outputlterator result,
UnaryOperation op) i
template <class Inputlteratorl,
class Inputlterator2, class Outputlterator,
class BinaryOperation>
Outputlterator transform(
Inputlteratorl firstl,
Inputlteratorl lastl,
Inputlterator2 first2,
outputlterator result,
BinaryOperation binary_op) i
}
552 B: The STL Reference
DESCRIPTION: The first form of the function transforms the sequence from
firstl up to lastl to a new sequence by applying the unary function ob-
ject op to each element. The resultant sequence is placed at the location re-
ferred to by result. The second form of the function transforms the
sequence from firstl up to lastl and the sequence from first2 up to
last2 into a new sequence by applying the binary function object op to
each pair of elements in tum. The resultant sequence is placed at the location
referred to by resul t. Both functions return an iterator that is one past the
end of the resultant sequence.
unary_function
TYPE: struct HEADER: <functional> [function.h]
namespace std{
template <class Arg, class Result>
struct unary_function;
}
Public Members:
unary_function<Arg, Result>: : argument_type;
unary_function<Arg, Result>::result_type;
class unary_negate
: public unary_function<
type name Predicate: :argument_type,bool>;
Public Methods:
explicit unary_negate<predicate>: : unary_negate (
const Predicate& pred);
Like all function adaptors, this defines opera tor ( ), which, when called, re-
turns the logical negation of pred (x) .
unique
TYPE: function HEADER: <algorithm> [algo.h]
TIME: linear SPACE: constant
namespace std{
template <class Forwardlterator>
Forwardlterator unique(FOrWardlterator first,
Forwardlterator last) ;
template <class Forwardlterator,
class Binarypredicate>
Forwardlterator unique(
Forwardlterator first,
Forwardlterator last,
BinaryPredicate binary_pred) ;
}
554 B: The STL Reference
valarray
TYPE: class HEADER: <numeric> []
namespace std (
template<class T>
class valarray;
Public Members:
valarray<T>: : value_type;
Public Methods:
Returns an array of the same length as the original whose members are the result
of applying the function func to each member of the original array.
These are the subscript operators. The const versions return a valarray that
can be used only as an r-value. The non-const versions return an instance of
a class that has reference semantics to the original array and can be used as
an I-value to reference the subscripted members.
These are the assignment operators. The first assigns the value of the scalar ar-
gument to each member of the array. The second assigns each member of
*this the value of the corresponding member of the argument array, which
must be of the same size. The remaining operators assign the results of a
generalized subscripting operation to the appropriate members of the array.
558 B: The STL Reference
valarray<T>::valarraY()i
explicit valarray<T>: :valarray(size_t n)i
valarray<T>: :valarray(const T& val, size_t n)i
valarray<T>: :valarray(const T* val,
size_t n) i
valarray<T>::valarray<T>: :valarray(
const valarray<T>&)i
valarray<T>: :valarray(const slice_array<T>&)i
valarray<T>: :valarray(const gslice_array<T>&)i
valarray<T>: : val array (const mask_array<T>&)i
valarray<T>: :valarray(const indirect_array<T>&)i
Replaces each member of *this by the bitwise exclusive OR of itself and the
corresponding member of a. *this and a must be of the same length. A
reference to the modified array is returned.
Replaces each member of *this by the bitwise AND of itself and the corre-
sponding member of a. *this and a must be of the same length. A refer-
ence to the modified array is returned.
Replaces each member of *this by the bitwise inclusive OR of itself and the
corresponding member of a. *this and a must be of the same length. A
reference to the modified array is returned.
Replaces each member of *this by itself, shifted left by the amount indicated
by the corresponding member of a. *this and a must be of the same
length. A reference to the modified array is returned.
560 B: The STL Reference
Replaces each member of *this by itself, shifted right by the amount indicated
by the corresponding member of a. *this and a must be of the same
length. A reference to the modified array is returned.
This changes the size of the array to s z and assigns the value c to all of the
members. All iterators and references to members of the array are
invalidated.
Returns a valarray equal to *this but with the values in the array by n posi-
tions, filling in the positions vacated by the shifted elements with T ( ). Ele-
ments shifted off the end of the array are lost. Positive values of n shift to
the left and negative values to the right.
Nonmember Functions:
template<class T>
valarray<T> abs(const valarray<T>& a)i
template<class T>
valarray<T> acos(const valarray<T>& a)i
Returns an array whose members are the arc cosine of the members of a.
template<class T>
valarray<T> asin(const valarray<T>& a)i
Returns an array whose members are the arc sine of the members of a.
template<class T>
valarray<T> atan(const valarray<T>& a)i
Returns an array whose members are the arc tangent of the members of a.
B: The STL Reference 561
template<class T>
valarray<T> atan2(const valarray<T>& x,
const valarray<T>& y)i
template<class T>
valarray<T> atan2(const valarray<T>& x, const T& y)i
template<class T>
valarray<T> atan2(const T& x, const valarray<T>& y)i
template<class T>
valarray<T> cos(const valarray<T>& a)i
template<class T>
valarray<T> cosh(const valarray<T>& a)i
Returns an array whose members are the hyperbolic cosine of the members of a.
template<class T>
valarray<T> exp(const valarray<T>& a)i
template<class T>
valarray<T> log(const valarray<T>& a)i
template<class T>
valarray<T> loglO(const valarray<T>& a)i
template<class T>
valarray<T>
operator*(canst valarray<T>& a,
canst valarray<T>& b)i
Returns an array whose members are the members of a multiplied by the corre-
sponding members of b.
template<class T>
valarray<T>
operator/(canst valarray<T>& a,
canst valarray<T>& b)i
Returns an array whose members are the members of a divided by the corre-
sponding members of b.
template<class T>
valarray<T>
operator%(canst valarray<T>& a,
canst valarray<T>& b)i
Returns an array whose members are the members of a modulo the correspond-
ing members of b.
template<class T>
valarray<T>
operator+(canst valarray<T>& a,
canst valarray<T>& b)i
Returns an array whose members are the members of a plus the corresponding
members of b.
template<class T>
valarray<T>
operator-(canst valarray<T>& a,
canst valarray<T>& b)i
Returns an array whose members are the members of a minus the corresponding
members of b.
template<class T>
valarray<T>
operatorA(canst valarray<T>& a,
canst valarray<T>& b)i
B: The STL Reference 563
Returns an array whose members are the members of a exclusive OR'ed with the
corresponding members of b.
template<class T>
valarray<T>
operator&(canst valarray<T>& a,
canst valarray<T>& b);
Returns an array whose members are the members of a bitwise AND'ed with the
corresponding members of b.
template<class T>
valarray<T>
operator I (canst valarray<T>& a,
canst valarray<T>& b);
Returns an array whose members are the members of a inclusive OR'ed with the
corresponding members of b.
template<class T>
valarray<T>
operator«(canst valarray<T>& a,
canst valarray<T>& b);
Returns an array whose members are the members of a shifted left by the corre-
sponding members of b.
template<class T>
valarray<T>
operator»(canst valarray<T>& a,
canst valarray<T>& b);
Returns an array whose members are the members of a shifted right by the corre-
sponding members of b.
template<class T>
valarray<T>
operator*(canst valarray<T>& a,
canst T& v);
template<class T>
valarray<T>
564 B: The STL Reference
operator*(const T& v,
const valarray<T>& a);
template<class T>
valarray<T>
operator/(const valarray<T>& a,
const T& v);
template<class T>
valarray<T>
operator/(const T& v,
const valarray<T>& a);
template<class T>
valarray<T>
operator%(const valarray<T>& a,
const T& v);
template<class T>
valarray<T>
operator%(const T& v,
const valarray<T>& a);
template<class T>
valarray<T>
operator+(const valarray<T>& a,
const T& v);
template<class T>
valarray<T>
operator+(const T& v,
const valarray<T>& a);
B: The STL Reference 565
template<class T>
valarray<T>
operator-(canst valarray<T>& a,
canst T& v);
template<class T>
valarray<T>
operator-(canst T& v,
canst valarray<T>& a);
template<class T>
valarray<T>
operatorA(canst valarray<T>& a,
canst T& v);
Returns an array whose members are the members of a exclusive OR'ed with v.
template<class T>
valarray<T>
operatorA(canst T& v,
canst valarray<T>& a);
Returns an array whose members are v exclusive OR'ed with the members of a.
template<class T>
valarray<T>
operator&(canst valarray<T>& a,
canst T& v);
Returns an array whose members are the members of a bitwise AND'ed with v.
template<class T>
valarray<T>
operator&(canst T& v,
canst valarray<T>& a);
Returns an array whose members are v bitwise AND'ed with the members of a.
566 B: The STL Reference
template<class T>
valarray<T>
operator I (canst valarray<T> a&,
canst T& v);
Returns an array whose members are the members of a bitwise OR'ed with v.
template<class T>
valarray<T>
operator I (canst T& v,
canst valarray<T>& a);
Returns an array whose members are v bitwise OR'ed with the members of a.
template<class T>
valarray<T>
operator«(canst valarray<T>& a,
canst T& v);
template<class T>
valarray<T>
operator«(canst T& v,
canst valarray<T>& a);
template<class T>
valarray<T>
operator»(canst valarray<T>& a,
canst T& v);
template<class T>
valarray<T>
operator»(canst T& v,
canst valarray<T>& a);
template<class T>
valarray<baal>
B: The STL Reference 567
operator==(const valarray<T>& a,
const valarray<T>& b);
Returns a Boolean array containing the result of applying opera tor== to the
corresponding elements of a and b. a and b must be of the same length.
template<class T>
valarray<bool>
operator!=(const valarray<T>& a,
const valarray<T>& b);
template<class T>
valarray<bool>
operator«const valarray<T>& a,
const valarray<T>& b);
Returns a Boolean array containing the result of applying opera tor< to the
corresponding elements of a and b. a and b must be of the same length.
template<class T>
valarray<bool>
operator>(const valarray<T>& a,
const valarray<T>& b);
Returns a Boolean array containing the result of applying opera tor> to the
corresponding elements of a and b. a and b must be of the same length.
template<class T>
valarray<bool>
operator<=(const valarray<T>& a,
const valarray<T>& b);
Returns a Boolean array containing the result of applying opera tor<= to the
corresponding elements of a and b. a and b must be of the same length.
template<class T>
valarray<bool>
operator>=(const valarray<T>& a,
const valarray<T>& b);
568 B: The STL Reference
template<class T>
valarray<bool>
operator&&(const valarray<T>& a,
const valarray<T>& b);
Returns a Boolean array containing the logical AND of the corresponding ele-
ments of a and b. a and b must be of the same length.
template<class T>
valarray<bool>
operator I I (const valarray<T>& a,
const valarray<T>& b);
template<class T>
valarray<T> pow(const valarray<T>& a,
const valarray<T>& b);
Returns a valarray whose members are the members of a to the power of the
corresponding members of b. a and b must be of the same length.
template<class T>
valarray<T> pow(const valarray<T>& a, const T& j);
template<class T>
valarray<T> pow(const T& j, const valarray<T>& a);
Returns a valarray whose members are each j to the power of the corre-
sponding member of a.
template<class T>
valarray<T> sin(const valarray<T>& a);
template<class T>
valarray<T> sinh(const valarray<T>& a);
B: The STL Reference 569
Returns a valarray whose members are the hyperbolic sine ofthe members of
a.
template<class T>
valarray<T> sqrt(const valarray<T>& a)i
Returns a valarray whose members are the square root of the members of a.
template<class T>
valarray<T> tan(const valarray<T>& a)i
template<class T>
valarray<T> tanh(const valarray<T>& a)i
Returns a valarray whose members are the hyperbolic tangent of the mem-
bers of a.
value_compare
TYPE: typedef REQUIRED BY: associative containers
inline T* value_type (
const bidirectional_iterator<T, Distance>&);
DESCRIPTION: a typedef that represents the type of the value stored in the
container.
vector
TYPE: class HEADER: <vector> [vector.h]
namespace std{
template <class T, class Allocator = allocator<T> >
class vector;
}
B: The STL Reference 571
Public Members:
vector<T, Allocator>::allocator_typei
vector<T, Allocator>::const_iteratori
vector<T, Allocator>::pointer;
Public Methods:
void vector<T, Allocator>: :assign(
size_type n,
const T& value);
template<class InputIterator>
void vector<T, Allocator>: :assign(
InputIterator first,
InputIterator last);
These methods erase the content of the vector and replace it with new values
specified by the parameters. The first form fills the vector with n copies of
value. The second form inserts the values from first up to last.
Returns a reference to the nth element of the vector. n must be less than
size().
Returns the number of elements that can be stored in the currently allocated
block of memory in which the vector is stored.
Returns an iterator that is one past the last element in the vector.
Deletes the element at the indicated position from the vector. The second form
deletes all elements in the range from first up to last. Returns an itera-
tor referencing the element after the last element deleted.
Inserts the element x just before pos i tion. The first form returns an iterator
that refers to the element inserted. The second form inserts the elements
from first up to last so that they are placed just before pos i tion.
The third form inserts n copies of the element x just before posi tion.
Returns the maximum number of elements that can be stored in the vector. This
usually corresponds to a system-imposed limit on the amount of memory
available for a contiguous block.
Assigns the value of the vector x to the object, adjusting the allocated space as
necessary.
A subscript operation that returns the nth element of the vector. The nth element
of the vector must exist, as this operator cannot be used to extend the limits
of the vector.
B: The STL Reference 575
Returns an iterator that can be used as the starting value for a reverse iterator.
Alters the size of the vector so that it is greater than or equal to n. This might re-
sult in storage reallocation.
Changes the size of the vector so that it contains size members. If the vector
has less than size elements, new elements are appended to make it the de-
sired length, with the new elements assigned val ue. If the vector has more
than size elements, elements are removed from the end to make it the de-
sired length. If the vector is the desired length, nothing is done.
Swaps the contents of this vector with the vector x so that each has the contents
of the other.
template<class Inputlterator>
vector<T, Allocator>::vector(
Inputlterator first,
Inputlterator last,
const Allocator& = Allocator(»;
vector<T, Allocator>::vector(
const vector<T, Allocator>& x);
Constructors to create a new vector. The first form constructs a vector and allo-
cates a buffer of default size to hold the elements. The second form allocates
a buffer large enough to hold n elements and initializes each of them to
value. The third form allocates a buffer large enough to hold the elements
from first up to last and then copies these elements into the newly allo-
cated space. The fourth form is the copy constructor.
Deletes all the elements stored in the vector and then deallocates the buffer in
which they were stored.
Nonmember Functions:
template <class T, class Allocator>
bool operator==(const vector<T, Allocator>& x,
const vector<T, Allocator>& y);
Returns true if the first vector is less than or equal to the second.
Returns true if the first vector is greater than or equal to the second.
vector<bool>
TYPE: class HEADER: <vector> [vector.h]
namespace std{
template <class Allocator>
class vector<bool, Allocator>;
578 B: The STL Reference
Public Members:
vector<bool, Allocator>: : allocator_type;
Public Methods:
void vector<bool, Allocator>: :assign(
size_type n,
const T& value);
template<class Inputlterator>
void vector<bool, Allocator>: :assign(
Inputlterator first,
Inputlterator last);
These methods erase the content of the vector and replace it with new values
specified by the parameters. The first form fills the vector with n copies of
value. The second form inserts the values from first up to last.
Returns a reference to the nth element of the vector. n must be less than
size().
Returns the number of elements that can be stored in the currently allocated
block of memory in which the vector is stored.
Returns an iterator that is one past the last element in the vector.
Deletes the element at the indicated position from the vector. The second form
deletes all elements in the range from first up to last. Returns an itera-
tor referencing the element after the last element deleted.
allocator_type vector<bool,Allocator>::
get_allocator() const;
Inserts the element x just before position. The first form returns an iterator
that refers to the element inserted. The second form inserts the elements
from first up to last so that they are placed just before position.
The third form inserts n copies of the element x just before pas i tion.
Returns the maximum number of elements that can be stored in the vector. This
usually corresponds to a system-imposed limit on the amount of memory
available for a contiguous block.
Assigns the value of the vector x to the object, adjusting the allocated space as
necessary.
A subscript operation that returns the nth element of the vector. The nth element
of the vector must exist, as this operator cannot be used to extend the limits
of the vector.
Returns an iterator that can be used as the starting value for a reverse iterator.
Alters the size of the vector so that it is greater than or equal to n. This might re-
sult in storage reallocation.
Changes the size of the vector so that it contains size members. If the vector
has less than size elements, new elements are appended to make it the de-
sired length, with the new elements assigned value. If the vector has more
than size elements, elements are removed from the end to make it the de-
sired length. If the vector is the desired length, nothing is done.
The first form swaps the contents of this vector with the vector x so that each has
the contents of the other. The second form swaps the values of x and y.
B: The STL Reference 583
template<class Inputlterator>
vector<bool, Allocator>: :vector(
Inputlterator first,
Inputlterator last,
canst Allocator& = Allocator());
Constructors to create a new vector. The first form constructs a vector and allo-
cates a buffer of default size to hold the elements. The second form allocates
a buffer large enough to hold n elements and initializes each of them to
value. The third form allocates a buffer large enough to hold the elements
from first up to last and then copies these elements into the newly allo-
cated space. The fourth form is the copy constructor.
Deletes all the elements stored in the vector and then deallocates the buffer in
which they were stored.
Nonmember Functions:
template <class baal, class Allocator>
bool operator==(const vector<bool, Allocator>& x,
const vector<bool, Allocator>& y);
Returns true if the first vector is less than or equal to the second.
Returns true if the first vector is greater than or equal to the second.
E
emptyO,287 indudesO, 141,439
endO, 50, 187 indirect_array, 174,440
equalO, 92, 427 infix, 288
equal_rangeO, 136, 428 inner_productO, 167,442
equal_toO, 67 inplace_mergeO, 139,443
eraseO, 193 input
iterator, 39, 43
F inpuCiterator, 444
inpuUterator_tag, 57
fillO, 101,428 insert_iterator, 302, 445
filCnO, 101, 429 inserterO, 445
Index 591
o Q
operator queue, 293,510
template, 64 priority, 157
operator>O,497
operator!=O,498 R
operator<=O, 498
operator>=O, 498 random_access_iterator,513
ostream_iterator, 43, 76 random_access_iterator_tag, 57
oucoCrange,14 random_shuffleO, 115,515
output range_error, 14
iterator, 40, 43 raw_storage_iterator, 320, 516
outpuciterator, 499 rbeginO, 50, 187
outpuUterator_tag, 57 recursion, 364
overflow_error, 14 reference, 366, 517
reference counts, 245
p removeO, 108,204,517
remove_copyO, 108,517
pair, 77, 92, 499 remove_copy_ifO, 108, 518
parser, 359 remove_if(), 108, 518
partiaCsortO, 122,501 rend(), 50, 187
partial_sort_copyO, 122,501 replaceO, 104, 519
partial_sumO, 169,502 replace_copyO, 105,519
partitionO, 116,503 replace30py_ifO, 105,520
permutations, 161 replace_ifO, 104, 520
plusO, 67, 504 reserveO, 193
Index 593
s T
scanner, 347, 359 teller, 329
search template, 7
binary, 130 temporary buffers, 324
linear, 5 thread safety, 23
searchO, 94, 526 timesO, 67, 553
search_n, 94 topO,287
searching, 129 transformO, 106, 553
sequence algorithms, 73 tree, 2-4, 271
set, 255 almost complete, 155
operations, 140 balanced, 269
seCdifferenceO, 527, 534 binary, 268
seUntersectionO, 147, 535 red-black, 272
seCsymmetric_differenceO, 150,536
set_unionO, 143,537 u
set difference, 148
shiftO,182 unary_function, 66, 557
size_type, 366, 537 unary_negate, 554
slice, 175,538 underflow_error, 14
slice_array, 177, 539 uninitialized30pyO,322
slist, 541 uninitialized_fillO, 322
sortO, 122,205,547 uninitializedjill_nO, 323
sorCheapO, 548 uninitialized memory, 320
sorting, 122 uniqueO, 110,205,347,555
partial, 123 unique_copyO, 110, 556
stable, 123 upper_boundO, 134,557
space-time trade-off, 190
spliceO, 204 v
stable_partitionO, 116,548
stable_sortO, 122,549 valarray, 172,557
stack, 286, 549 value30mpare, 366, 571
standard exceptions, 13 value_type, 366, 572
Stepanov, Alexander, 3 value_typeO, 57, 571
vector, 52, 189,572
594 Index
vector<bool>, 197,579
w
wstring, 237