• No results found

The Standard Template Library

C++ is an object-oriented language, but recent extensions to the language bring C++ to a higher level. The most significant addition to the language is the Standard Tem- plate Library (STL), developed primarily by Alexander Stepanov and Meng Lee. The library includes three types of generic entities: containers, iterators, and algorithms. Al- gorithms are frequently used functions that can be applied to different data structures. The application is mediated through the iterators that determine which algorithms can be applied to which types of objects. The STL relieves programmers from writing their own implementations of various classes and functions. Instead, they can use prepack- aged generic implementations accommodated to the problem at hand.

1.7.1 Containers

A container is a data structure that holds some objects that are usually of the same type. Different types of containers organize the objects within them differently. Although the number of different organizations is theoretically unlimited, only a small number of them have practical significance, and the most frequently used organizations are in- corporated in the STL. The STL includes the following containers: deque, list, map, multimap, set, multiset, stack, queue, priority_queue, an d vector.

The STL containers are implemented as template classes that include mem- ber functions specifying what operations can be performed on the elements stored in the data structure specified by the container or on the data structure itself. Some operations can be found in all containers, although they may be implemented dif- ferently. The member functions common to all containers include the default con- structor, copy constructor, destructor, empty(), max_size(), size(), swap(), operator=, and, except in priority_queue, six overloaded relational operator functions (operator<, etc.). Moreover, the member functions common to all containers except stack, queue, and priority_queue include the functions be- gin(), end(), rbegin(), rend(), erase(), and clear().

  S e c t i o n   1 . 7   T h e   S t a n d a r d   T e m p l a t e   L i b r a r y    ■   25

Elements stored in containers can be of any type, and they have to supply at least a default constructor, a destructor, and an assignment operator. This is particularly important for user-defined types. Some compilers may also require some relational operators to be overloaded (at least operators == and <, but maybe != and > as well) even though the program does not use them. Also, a copy constructor and the func- tion operator= should be provided if data members are pointers because insertion operations use a copy of an element being inserted, not the element itself.

1.7.2 Iterators

An iterator is an object used to reference an element stored in a container. Thus, it is a generalization of the pointer. An iterator allows for accessing information included in a container so that desired operations can be performed on these elements.

As a generalization of pointers, iterators retain the same dereferencing notation. For example, *i is an element referenced by iterator i. Also, iterator arithmetic is similar to pointer arithmetic, although all operations on iterators are not allowed in all containers.

No iterators are supported for the stack, queue, and priority_queue con- tainers. Iterator operations for classes list, map, multimap, set, and multiset are as follows (i1 and i2 are iterators, n is a number):

i1++, ++i1, i1--, --i1 i1 = i2

i1 == i2, i1 != i2 *i1

In addition to these operations, iterator operations for classes deque and vector are as follows:

i1 < i2, i1 <= i2, i1 > i2, i1 >= i2 i1 + n, i1 - n

i1 += n, i1 -= n i1[n]

1.7.3 Algorithms

The STL provides some 70 generic functions, called algorithms, that can be applied to the STL containers and to arrays. A list of all the algorithms is in Appendix B. These algorithms are implementing operations that are very frequently used in most pro- grams, such as locating an element in a container, inserting an element into a sequence of elements, removing an element from a sequence, modifying elements, comparing elements, finding a value based on a sequence of elements, sorting the sequence of ele- ments, and so on. Almost all STL algorithms use iterators to indicate the range of ele- ments on which they operate. The first iterator references the first element of the range, and the second iterator references an element after the last element of the range. There- fore, it is assumed that it is always possible to reach the position indicated by the second iterator by incrementing the first iterator. Here are some examples.

The call

randomly reorders all the elements of the container c. The call i3 = find(i1, i2, el);

returns an iterator indicating the position of element el in the range i1 up to, but not including, i2. The call

n = count_if(i1, i2, oddNum);

counts with the algorithm count_if() the elements in the range indicated by itera- tors i1 and i2 for which a one-argument user-defined Boolean function oddNum() returns true.

Algorithms are functions that are in addition to the member functions provided by containers. However, some algorithms are also defined as member functions to provide better performance.

1.7.4 Function Objects

In C++, the function call operator () can be treated as any other operator; in particu- lar, it can be overloaded. It can return any type and take any number of arguments, but like the assignment operator, it can be overloaded only as a member function. Any ob- ject that includes a definition of the function call operator is called a function object. A function object is an object, but it behaves as though it were a function. When the func- tion object is called, its arguments become the arguments of the function call operator. Consider the example of finding the sum of numbers resulting from applying a function f to integers in the interval [n, m]. An implementation sum() presented in Sec- tion 1.4.5 relied on using a function pointer as an argument of function sum(). The same can be accomplished by first defining a class that overloads the function call operator:

class classf { public:

classf() { }

double operator() (double x) { return 2*x;

} }; and defining

double sum2(classf f, int n, int m) { double result = 0;

for (int i = n; i <= m; i++) result += f(i);

return result; }

which differs from sum() only in the first parameter, which is a function object, not a function; otherwise, it is the same. The new function can now be called, as in

classf cf;

  S e c t i o n   1 . 7   T h e   S t a n d a r d   T e m p l a t e   L i b r a r y    ■   27

or simply

cout << sum2(classf(),2,5) << endl;

The latter way of calling requires a definition of constructor classf() (even if it has no body) to create an object of type classf() when sum2() is called.

The same can be accomplished without overloading the function call operator, as exemplified in these two definitions:

class classf2 { public:

classf2 () { }

double run (double x) { return 2*x;

} };

double sum3 (classf2 f, int n, int m) { double result = 0;

for (int i = n; i <= m; i++) result += f.run(i); return result;

} and a call

cout << sum3(classf2(),2,5) << endl;

The STL relies very heavily on function objects. The mechanism of function pointers is insufficient for built-in operators. How could we pass a unary minus to sum()? The syntax sum(-,2,5) is illegal. To circumvent the problem, the STL defines function objects for the common C++ operators in <functional>. For example, unary minus is defined as

template<class T>

struct negate : public unary_function<T,T> { T operator()(const T& x) const {

return -x; }

};

Now, after redefining function sum() so that it becomes a generic function: template<class F>

double sum(F f, int n, int m) { double result = 0;

for (int i = n; i <= m; i++) result += f(i);

return result; }

the function can also be called with the negate function object: sum(negate<double>(),2,5).