Object-oriented programming B, Lecture 11e. 1

Chapter 5. The Standard Library.

5.1. Overview of main STL components.

The Standard Template Library (STL) has been developed by Alexander Stepanov, Meng Lee, and David Musser at Hewlett-Packard Research Labs in the early nineties. Today STL becomes a part of the Standard ++ Library together with I/O streams, the , etc. STL is a general purpose library of generic algorithms and generic data structures - containers that can be plugged together and used in an application. Special STL components, called and being generalized C/C++ pointers, “connect” containers and algorithms and allow them working together effectively. The STL containers include the most commonly used and most valuable data structures, such as vectors, deques, lists, sets, and maps. The template algorithm components include a broad range of fundamental algorithms for the most common kinds of data manipulations, such as searching, sorting, and merging. The STL generally avoids inheritance and virtual functions in favour of using templates.

STL contains six major kinds of components: first of all, three key STL components - containers, generic algorithms, and iterators, as well as three auxiliary ones - function objects, adapters, and allocators ( Fig. 5.1).

Adaptors

Containers Function objects

Iterators

Allocators Algorithms

Fig. 5.1. The STL components and their main relationships.

All STL components are defined in the namespace std.

Containers are used to manage collections of objects of certain type. Different containers have different abilities, such as internal (arrays, linked lists, binary trees), category of iterators provided, effectiveness of inserting/removing elements, and so on. Containers have own member functions to provide some restricted processing own elements (inserting/removing elements, etc.).

Iterators are used to step through the elements of collections of objects. These collections may be containers or subset of containers. Iterators offer common interface for any arbitrary container type. For example, the operator++ lets the step to the next element in the collection. This is done independently of the internal structure of the collection (array, , etc). This is because every container class provides its own iterator type being actually a kind of that translates the call "go to the next element" into whatever is appropriate.

Algorithms are used to provide broad scope of processing the elements of collections. For example, they can search, sort, or simply use the elements for different purposes. Object-oriented programming B, Lecture 11e. 2

Algorithms use iterators, therefore an algorithm has to be written only once to work with arbitrary containers because the iterator interface is common for all container types. Most of the STL algorithms are provided in the standard header , and some in the header .

Function objects are used in algorithms as functional arguments, and in sorted associative containers as template parameters. The STL function objects covers fundamental operations: arithmetic, relational, and logical. They are provided in the standard header .

Adaptors are used for modifying the interface of another components - containers, iterators, and function objects. For example, the container adaptors using the fundamental container classes are actually new containers with specific abilities: stack, queue, and . Another example is so-called binders - a category of function adaptors that help us construct a wider variety of function objects.

Allocators are used in STL instead of for the allocation and deallocation of memory and represent a special memory model. Every STL container class uses a class to encapsulate information about the memory model used: pointers/constant pointers, references/constant references, sizes of objects, allocation and deallocation functions, etc. The STL defines a default allocator being sufficient in most programs.

5.2. Introduction to the STL containers.

In STL, containers are objects that store collections of other objects, all of the same type T. There are two categories of fundamental (so-called first-class) STL container types: - sequence containers; - sorted associative containers. The STL provides also three above-mentioned container adaptor classes - stack, queue, and priority_queue that modify (adapt) the interface of an underlying first-class container to behave in a particular way (e.g., stack adaptor creates a LIFO list).

5.2.1. Sequence containers. Computational complexity.

Sequence containers are ordered collections in which every element has a certain position. This position depends on the time and place of the insertion, but is independent of the value of the element. That is, sequence containers provide a strictly linear arrangement that preserves the relative positions into which the elements are inserted.

A few words about terminology. A sequence (often called a range) is fixed by the pair of iterators: first iterator refers to the first element of the sequence, and the second refers to a one-beyond-the-last hypothetical element (Fig. 5.2). Such a sequence is called “half open”, and traditional mathematical notation for a half-open range is [first, last).

… next position after the end of the container

first last

Fig. 5.2. A sequence (range) delimited by the pair of iterators.

For algorithms that work on containers of size n, we can consider the maximum time (or the worst-case time) T(n) that an algorithm could take. Instead of trying to develop a precise formula for T(n), we can find a simple asymptotic function that provides an upper bound on the function T(n). For example, we might have the following inequality: T(n) ≤ cn, Object-oriented programming B, Lecture 11e. 3

for some constant c and all sufficiently large n (say, for n ≥ N ). This relation can be written using so-called big Oh notation: T(n) = O(n). In this case algorithm is said to have a linear time bound or, more simply, to be linear time.

If T(n) ≤ c (or T(n) = O(1)), the algorithm is said to have a constant time bound or to be constant time. In this case we can talk about so-called random access. It means that the time to reach the nth element of the sequence is constant, i.e., time does not depend on n.

The following graphs illustrate these cases:

T c*n

T(n) c c T(n)

time T - running N n N n

a) constant time bound b) linear time bound

Fig. 5.3. Asymptotic estimations of the computational complexity

The STL first-class types are as follows: - vector, providing random access to any element of a sequence of varying length, with rapid (constant time) insertions and deletions at the end;

- deque, (it's pronounced deck) also providing random access to any element of a sequence of varying length, with rapid insertions and deletions at front or back;

- list, doubly- providing only linear-time (O(n))access to a sequence of varying length, but with rapid insertions and deletions at any position in the sequence.

The header files for these containers are as follows: , , . Actually, the STL involves two other sequence containers: - T a[n], that is, ordinary C++ array types, which provide random access to a sequence of fixed length n; - the class basic_string (or simply string) placed in the header . Unlike ordinary arrays, class string provides the STL container interface. The class string objects can be considered as containers of characters.

5.2.2. Sorted associative containers.

Sorted associative containers provide ability for fast retrieval of objects from the collection based on keys that stored in the item (or in some cases are the items themselves). The sorted associative containers are based on one general approach to associative retrieval, which is to keep keys sorted according to some total order, such as numeric order if the items are numbers or lexicographical order if the keys are strings, and use a binary search algorithm. These containers can be implemented with balanced binary search trees. (another approach is hashing.) STL has four sorted associative container types: - , which supports unique keys (contains at most one of each key value) and provides for fast retrieval of the keys themselves; therefore, each element serves as both a sort key and a value; Object-oriented programming B, Lecture 11e. 4

- multiset, which supports duplicate keys (possibly contains multiple copies of the same key value) and provides for fast retrieval of the keys themselves;

- map, which supports unique keys (of type Key) and provides for fast retrieval of another type T (mapped value) based on the keys (rapid key-based lookup); a map is also called an . An associative array can be thought as an array for which the index need not be an integer.

- multimap, which supports duplicate keys (of type Key) and provides for fast retrieval of another type T based on the keys; multimap allows duplicate elements for a given key and is also called a dictionary. A good example of multimap could be a phone book, since one person can have several phone numbers.

So, set and multiset can be seen as degenerate associative arrays/dictionary in which no value is associated with a key. Both map and multimap are contained in the header file , as well as both set and multiset are contained in the header file .

5.2.3. Common container operations.

Each STL container has associated member functions, like push_back in the vector container. They ensure minimal functionality. Some of them are common for all containers. They include, in particular: • default constructor to provide a default initialization of the container; • copy constructor that initialize the container to be a copy of an existing container of the same type; • operator = that assigns one container to another; • empty that returns true if there are no elements in the container, false otherwise; • size that returns the number of elements currently in the container, and so on. There are also member functions that are only found in first-class containers, such as • begin - returns an iterator (or constant iterator) referring to the first element of the container; • end - returns an iterator (or constant iterator) referring to the next position after the end of the container; • erase - removes one or more elements from the container; • clear that removes all elements from the container (makes the container empty). • relational and equality operators.

It is important to take into account, when an element is inserted into a container, a copy of that element is made. Therefore, if default member-wise copy does not perform a proper copy operation for the element type, the element type must provide its own copy constructor and assignment operator.

5.2.4. The STL class template pair.

The definition of the class template pair is included in the STL header file containing several templates of general utility. Pair's most important members are shown here: template struct std::pair { //type definitions: typedef T1 first_type; typedef T2 second_type;

//data members: T1 first; T2 second; Object-oriented programming B, Lecture 11e. 5

//default and parameterized constructors: pair(): first(T1()), second(T2()) {} pair(const T1& x, const T2& y): first(x), second(y) {}

//the template version of a copy constructor with implicit //conversions opportunity: template pair(const pair& p) :first(p.first), second(p.second){} }; //convenience function to create a pair: template pair make_pair(const T1&, const T2);

A pair is by default initialized to the default values of its element types. For example, strings are initialized to the empty strings. A pair is used in a map/multimap containers, where the key is the first element of the pair and the mapped value is the second.

5.3. Vectors.

Vectors are sequence containers that provide fast random access to any element of a sequence of varying length via the subscript operator [] exactly like a C/C++ "raw" array; fast insertions and deletions at the end are provided as well. The sequence is stored as an array of type T. . A vector models a :

Fig.5.4. Structure of the STL vector.

The class declaration contains type definitions and member functions (incl. constructors) declarations in its public section: template > class vector { public: //types definitions: … //constructors: … //access member functions and others: … protected: A allocator; };

5.3.1. Vectors constructors.

Vectors have actually 5 kinds of constructors: - the default constructor, i.e. a constructor function that can be called without supplying an argument, produces a sequence that is initially empty (contains no elements); it is used in expressions in the form: vector(), e.g. vector* pV = new vector();

or in declarations in the form: vector object_name; e.g. vector myVector; Object-oriented programming B, Lecture 11e. 6

- the parameterized constructors of three kinds: - the first one being used in expressions in the form vector(n) or in a declaration in the form vector object_name(n); actually, this kind of a parameterized constructor produces a sequence initialized with n copies of the result of calling the default constructor of type T, that is T().

- the second kind is expressed by vector(n, value) or vector object_name(n, value); it produces a sequence initialized with n copies of value, which must be of type T.

- the third kind is declared as vector(const_iterator first, const_iterator last , const A& a1 = A()); that constructs a vector of size last - first and initializes it with copies of elements in the range [first, last) of another vector or array.

- the copy constructor being declared as usual: vector(const vector& x); and used in statements like vector v1(v0); // where v0 is an object of type // vector or vector* pVS = new vector(v2); //where v2 is an object of type vector

The code below shows the usage of the constructors mentioned: Example 5.1. 01: #pragma warning(disable:4786) // Disable warning message 4786 02: #include 03: #include 04: #include 05: #include 06: using namespace std; 07: 08: int main() 09: { 10: //using default constructor: 11: vector v0; 12: assert(v0.size() == 0 && v0.empty() == true); 13: vector* pV0 = new vector(); 14: assert( pV0->empty() == true ); 15: 16: //using parameterized constructor - 1st kind: 17: vector v1(3); 18: assert(v1.size() == 3 && v1[0] == int()); 19: 20: //using parameterized constructor - 2nd kind: 21: vector v2(2, "data"); 22: cout << v2[0] << " " << v2[1] << endl; 23: assert(v2[0] == "data"); 24: 25: //using parameterized constructor - 3rd kind: 26: const int SIZE = 3; 27: int a[SIZE] = {3, 5, 7}; 28: vector v3(a, a + SIZE); 29: for(int i = 0; i < SIZE; i++) 30: cout << v3[i] << " "; // 3 5 7 Object-oriented programming B, Lecture 11e. 7

31: cout << endl; 32: 33: for(int* j = v3.begin(); j < v3.end(); j++) 34: cout << *j << " "; 35: cout << endl; // 3 5 7 36: 37: //using copy constructor: 38: vector v4(v2); 39: vector* pV4 = new vector(v2); 40: assert(v4[0] == (*pV4)[0]); 41: cout << (*pV4)[1] << endl; // data 42: 43: //copy constructor and pair objects: copy constructors are called 44: vector< pair > v41; 45: v41.push_back(pair(1,"one")); 46: vector< pair >* 47: pV41c = new vector< pair >(v41); 48: cout << (*pV41c)[0].first << " " << (*pV41c)[0].second 49: << endl; //1 one 50: return 0; 51: }