Standard Template Library

Container classes

Containers are classes that are used to hold other objects. For example, a list is a container that holds data and links the data objects together. There are a number of container classes in the STL.

Vector example

Vectors grow when you add elements. They are not pre-allocated like arrays. So you can't insert randomly. Before you can insert v[10] you must insert v[0-9]. Vectors are different from lists in that you can randomly access elements. The vector size is the actual number of elements in the vector. The capacity of a vector may be larger than the size as the vector will allocate a few extra spaces when it resizes to add an element.

Iterators

An iterator can be seen as a kind of pointer. A standard C pointer is a direct memory reference to the base object. So a character pointers value is the address of the character being pointed at. The * operator is used to fetch and store values through the pointer. In C, the functionality cannot be changed. The * operator can only fetch or store data at a particular memory location. Incrementing a pointer performs arithmetic on the memory address to get the next address. If the programmer increments a pointer by one, the actual amount of the addition is the size of the pointed at thing. So, if float * ptr;
then p++;
is equivalent to p = p + sizeof(float);

This means that incrementing the pointer is only meaningful as an iterator over arrays.

Iterator classes

To use the concept of an iterator on on other containers, you can create a new class to represent the iterator. For example, to create an iterator over a linked list, it might look like class iterator {
public:
iterator (node *p=0) {ptr=p};
iterator operator++(int);
char * operator*() {return(ptr->value);};
node * get_ptr() { return(ptr);};
bool not_null() { return(ptr != 0);};
private:
   node *ptr;
} iterator
iterator::operator++(int)
{
   if (ptr != 0)
      ptr=ptr->next;
   return *this;
}

Why not just overload the ++ and * operators on the list class rather than create a whole class? Functionally, there isn't much difference. Putting the operators in the list class, kind of merges two kinds of objects. The list class encapsulates all the information we have about the list in one place. Including the iterator operations in there mushes the two concepts. Creating an iterator class encapsulates what we know about the pointers in its own class. This is a little vague. The usefulness of a separate iterator class should become clearer when we talk about iterator adaptor classes. The key feature of an iterator over a simple pointer is the ability to assign arbitrary code to the operators * and ++. Again, this will be clearer in iterator adaptors. Iterators are used heavily in the algorithms in the STL. This is because they can be defined so that all iterators implement the same methods even though they are iterating over many different types.

Iterator Use

Iterators are provided to move around on a vector. The range of iterators in STL includes the first thing in the range but not the last. This is so that loops over the vector will stop when iterator points at the end value and will still have evaluated the last element.

Iterators are like pointers, you dereference the pointer using the * operator. You can add to the end of a vector using the push_back() method but there is no push_front() method. Some examples are: vector v;
vector::iterator i=v.begin(); // i points to the first element in v
i++; // move to the next element
i--; // move to the previous element
v[3]=9; // set element 3 to 9

Iterator Adapters

Iostreams are similar to iterators in that they allow access to a sequence of elements. Every time you refer to cin, you get the next element in the input buffer. This is similar to the iterator actions of p++;
x=*p;

But iterators are supposed to be incrementable and dereferencable. If cin were an iterator, you would expect to do cin++ to go to the next input element and *cin to fetch it.

When we have seen problems like this before, we have overloaded the operators to make them act they way we want to. We will do that here by building an adaptor class to make the iostream look like an iterator class. The STL contains an adaptor class called istream_iterator that overloads the increment and dereference operators so that cin++ doesn't do anything. The code probably looks like istream_iterator & operator++() {};

The * operator simply reads the next thing from the input stream. These iterator adaptors can be used on any input stream including istrstreams and ifstreams.

A common use of iterator adaptors is in some of the STL algorithms that use iterator ranges. For example, the copy() functions moves the objects in the range defined by the first two arguments to the third. vector v1,v2;
// some code to put stuff in v1
copy(v1.begin(),v1.end(),v2.begin());

Starting at the first element in v1, copy it to the first element in v2. Increment both pointers and do it again until we reach the end pointer on v1. One thing you have to be careful about when using the copy() function this way is that it assumes that the destination has the space needed to put the source elements in. A little later we will see a way to make this use push_back() to add elements to the destination.

Now if we could use standard output as an iterator, we could use copy() to send the elements of an array to the output. The standard way to do this is for(int i=0;i < v.size; i++)
      cout << v[i];

We can use an ostream_iterator to convert the standard output operator to an iterator. ostream_iterator o(cout) is how you declare an adaptor. The second type to the template can usually be left out. It refers to the memory model needed for compilers that have memory models. The template should default to ptrdiff_t. If it complains, use ptrdiff_t.

Here is an example of using copy to write out a vector. copy(v.begin(),v.end(), ostream_iterator(cout,"\n"));

The second argument to the adaptor is a separator that is printed after each element.

Input can be done as well. copy(istream_iterator(cin),istream_iterator(), v.begin());

This still presents the problem of making sure there is enough space in the vector before running this. Another kind of adaptor can be used to fix this. copy(istream_iterator(cin),
     istream_iterator(),
     back_insert_iterator>(v));

The back insert adaptor turns the dereference calls into calls to the push_back() method on vectors.

This example really shows the ability to use classes and operator overloading to do things that would be very difficult in C.

Function Application

Some of the algorithms in the STL use iterators to apply a function to each of the elements in the container. So if we had a function
int zero() {return 0;};
Then a call to
generate(v.begin(),v.end(),zero);
would run each element of v through the zero function and stores the result back in the element. The for_each functions applies the function to each element, but doesn't store the result back in the element. the find_if function applies the function and returns as soon as one of the function calls returns true. It returns an iterator that points to the element that matched the function.