10Generic Programming and the Standard Template Library

In Chapter 9, we introduced several basic collections and related algorithms. There are many other types of commonly used collections and complex algorithms. The study of these issues is the subject of data structure. It is not necessary for programmers to develop every class and algorithm from scratch. In fact, the standardization and modulization of commonly used algorithms are the keys to fast software production. To achieve this, we need to use not only concepts from object-oriented programs but also those from generic programming. The Standard Template Library (STL) in C++ is a good example of integrating these two concepts.

In this chapter we introduce the concepts, the structures, and the usage of STL. We briefly introduce the basic applications of containers, iterators, algorithms, and function objects to get an overall picture of STL. We recommend that readers refer to other books for other topics in STL and generic programming. Grasping some basic knowledge in data structure helps to understand the materials in this chapter.

10.1Generic Programming

10.1.1Introduction

The goal of generic programming is to represent abstract concepts and algorithms in generic codes without losing efficiency. Generic programming is a different technique from object-oriented programming. In some cases, there are even conflicts between good generic programming design and good object-oriented programming design. But the combination of these two techniques results in a powerful tool for building complex software systems. STL is an excellent example of it. For example, with the help of STL, programmers do not need to write array classes for integers, reals, and characters; writing a generic template is sufficient. These techniques allow heavy code reuses and improve the efficiency of software production.

Recall the array, linked list, and the queues template implementation, searching, and sorting algorithm discussed in Chapter 9. In object-oriented programming, it is good practice to encapsulate these algorithms into classes. However, a better solution is to write generic algorithms independent of implementation such as “finding an element sequentially in a linear collection”. That is the goal of generic programming.

The iterator is the adaptive layer between the algorithms and the data structure. Algorithms can use the iterator interface to access the data structure to perform specific operations. That is the key scheme for decoupling the data structure and the algorithm used by generic programming.

Fig. 10.1: Relationship between STL components.

STL is the core component of the C++ standard library. STL implements general containers and iterators, which are the basis of algorithm templates. There are four key components in STL: containers, iterators, algorithms, and function objects as shown in Figure 10.1. As indicated, the algorithm is in the center of the structure. The algorithm uses iterators to obtain data from the container, operates on it with the function objects, and then sends the result to another container through another iterator.

10.1.2Namespace

There are many modules in complex software, including different libraries and classes provided by different programmers. These might contain naming conflicts among these modules, i.e., different things represented by the same identifier. A namespace is the way of resolving this problem in C++.

A namespace restricts a set of identifiers in a named scope. So it is possible to have the same identifier in a different named scope. Programmers use “the name of namespace” + “::” to refer an object in the named scope. For example, the following code declares a namespace NS and some identifiers in it:

The way to refer to these identifiers is the following:

Fig. 10.2: UML graph of packages.

There is a special anonymous namespace that can be used directly. Most examples in this book do not declare namespace explicitly, so the identifiers in these examples are in the scope of the default anonymous namespace.

Programmer can write the “using” statement to avoid writing the lengthy prefixes of the identifiers. For example, if programmer writes the following statement in the code:

then all references of File in the current scope mean NS::File.

However, if the programmer writes the following statement:

then the program can use all identifiers in the named scope of NS without any prefixes.

The standard library in C++ is in the namespace “std”. Programmers can refer to identifiers in the standard library directly with the “std::” prefix, e.g., “std::cout”, or use the “using” statement to enable the program to use these identifiers directly.

Namespaces are represented in a package graph in UML. A package contains classes and other elements, whose identifiers must be unique. All elements in the package are destroyed if the package is destroyed.

Packages are represented by folders in a UML graph. The inner classes of the package can be hidden by writing the package name in the middle of the folder. To show the details of the package, the package name should be written at the label part of the folder. Figure 10.2 shows how to represent a package in these two ways.

The dependencies between packages represent the references among objects in different namespaces. The access dependency describes a package with the privilege of accessing all public interfaces in another package. For example, objects in package P2 are able to access class C in P1 if class C is in the public interface. The relationship between P2 and C is an access dependency, as shown in Figure 10.3.

Import dependency represents merging the public interfaces between two packages. For example, if there is an import dependency between P1 and P2, then P2 can use class C directly, i.e., adding “using namespace P1” in P2’s codes.

Fig. 10.3: UML graph for packages.

10.1.3Differences of Naming Conventions Between C/C++

The C++ standard library declares all identifiers in the namespace “std”. There is no extension name in the header filename; for example, the following statement includes the string library in the program:

It also works for C’s standard library, but the programmer should add the prefix “c” rather than the suffix “.h”:

The old style of the include statement still works in order to be backwards compatible with C:

Notice that the header declares the identifiers in both the global namespace and the “std” namespace.

The STL library is scattered in a series of header files. Here is a quick reference of objects in STL:

  1. Vector, lists, and double queues are in <vector>/<list>/<deque> respectively.
  2. Sets and multisets are in <set>, maps and multimaps are in <map>.
  3. Stacks are in <stack>. Queues and priority queues are in <queue>.
  4. Basic algorithms are in <algorithm>, some generic numeric algorithms are in <numeric>.
  5. Iterators and the adapters of iterators are in <iterator>.
  6. Function objects and function adapters are in <functional>.

10.1.4Concepts of STL

10.1.5Containers

Containers are objects that contain a set of objects. There are two kinds of containers: heterogeneous containers where the container can hold elements of different types; and homogenous containers where the container can only hold elements of the same type.

There are seven types of containers in the STL library: vector, double queue, list, set, multiset, map, and multimap. They can be divided into two groups: sequential containers and associative containers. Sequential containers, such as vectors, double queues, and lists organize elements linearly, while associative containers, such as sets, multisets, maps, and multimaps organize elements based on indexes.

10.1.6Adapters

Adapters provide new interfaces for existing classes and enable two classes to work cooperatively. There are three types of adapters in STL: adapters of containers, adapters of iterators, and adapters of function objects. Adapters are very powerful tools for code reuse. For example, STL uses adapters of containers to implement stacks, queues, and priority queues based on corresponding containers.

10.1.7Iterators

Iterators can be seen as pointers in an object-oriented view. Iterators provide methods like current(), previous(), and next() to allow programmers to access the container via the iterators.

10.1.8Algorithms

There re more than 70 algorithm implementations in STL, including sorting, eliminating, counting, comparing, transforming, and container managing, which covers a wide range of applications. The implementations are generic so that they can be applied to different objects and primitive types.

10.1.9Interfaces of Containers

There are several common interfaces in the STL containers. These containers overload the comparison operator to perform comparison between containers (Table 10.1). There are also methods to access iterators (Table 10.2) and methods to access the status of the container (Table 10.3).

Tab. 10.1: All Operators in STL Containers.

General Container Operator Description
a==b Equal operation for same type container
a!=b Unequal operation for same type container, the same as !(a==b)
a>b If b<a, return boolean type “true”
a>=b If !(a<b), return boolean type true
a<b If a<b, return boolean type true
a<=b If !(b<a), return boolean type true
r=b Assign value operation for a container

Note: x is a container class, and a and b are instances of class x, r is the value of type x&.

Tab. 10.2: Iterator and Access Methods of Each Container.

Iteration Function Description
begin() Return an iterator pointing to the first element in this container
end() Return an iterator pointing to the last element in this container
rbegin() Return reverse_iterator(end()), a reverse iterator which points to the first element of the reversed elements.
rend() Return reverse_iterator*(begin()), a reverse iterator which points to the last element of the reversed elements
Access Methods Description
size() Return the number of elements
max_size() Return the maximum amount of elements that the container could hold
swap() Swap two containers which are of the same type
empty() If the container is empty or size()==0, return true

Tab. 10.3: Immutable Access to Container Elements.

Immutable Method Description
front() To access the header element of the container
back() To access the tail element of the container
[] operand To access any element in the container directly

10.2Containers in STL

STL containers have been introduced briefly in the previous section. This section will discuss containers in detail.

Sequential containers organize elements linearly, meaning that the program can define the positions of elements as well as the top/ bottom of the container. You can see sequential containers as an implementation of linear collection. It is a totally different story for associative containers, which organize elements based on indexes.

There are two access patterns in sequential containers: sequential access and random access. You have to access the element sequentially in a container that only supports sequential access, i.e., you have to access the first and the second element before accessing the third one. However, you can access the elements directly if the container supports random access. Vectors and double queues are random-access containers while lists are sequential-access containers.

There are four associative containers: set, multiset, map, and multimap. An element can be accessed based on its key. Interested readers can refer to books about data structure and STL for more details.

There are seven containers in STL, and they provide the functions of storing, searching, and accessing elements. Besides, stacks, queues, and priority queues are standard data structures, and are necessary in many applications. STL provides three kinds of module called adaptors. Combining adaptors and sequential containers can provide the functions of stacks, queues, and priority queues.

10.2.1Sequential Containers

The most widely used sequential container in C++ is the array. However, sometimes more powerful tools are needed. To meet this need, STL implements three sequential containers: vectors, lists, and double queues.

The vector is a powerful extension of the static array. It is very similar to the static array, but it can enlarge its storage on demand at runtime.

The list is an implementation of the double-linked list. It is quick to add or remove an element to or from a list.

The double queue is an extension of the data structure queue. Compared to a standard queue, it can add or remove elements from both ends of the queue.

1.Common Interfaces of Sequential Containers

a)Adding Elements

There are a number of ways to insert elements into a container. The first one is to use the push_front() or push_back() methods, which add an element at the top or at the end of the container respectively.

The second way is to use the insert() method. It has some variants in different containers. Insert (L,O) adds element O before location L. Insert (L,N, O) inserts element O before location L N times. Insert (L, i, j) adds all element between location i and location j before location L. It is worth mentioning that the insert() method will possibly invalidate all iterators in vectors and double queues, so iterators and insert() methods should be used with care.

The third way is to use the assignment operator to add all elements in a container into an empty container.

These three ways are standard ways to add elements into a sequential container.

Calling push_front() or push_back, using insert(), and using the assignment operator are the three standard ways to add elements to a sequential container.

b)Removing Elements

There are also a number of ways to remove elements in a container. We should notice that to remove elements, the destructor of elements should be called rather than the container’s destructor. The first method is to call pop_front() or pop_back(). They remove the element at the head and the element at the tail of the container, respectively.

The second way is to call the erase() method. Erase(L) removes the element at location L; erase(L1, L2) removes all elements between locations L1 and L2.

The third way is to call the clear() method,which removes all elements in the container.

Removing elements will also invalidate the iterators. Erase() and clear() will invalidate the iterators pointing to the deleted elements in list container. The effect of removing elements from the head or the tail of the double queue is the same as the one in the list, but removing an element from the middle of a double queue will invalidate all iterators. Removing elements in vectors also invalidate all iterators.

c)Traversing the Sequential Container

The iterator is the object-oriented approach to traverse a container. It overloads the dereference operator “*” to return the reference of the element that it points to, just as a normal pointer.

d)Other Ways to Access Elements in a Sequential Container

Programmer can use methods front() and back() to access the element at the head or at the tail of the container respectively. Subscript operator “[]” can also be used to directly access elements in the container, which is only available in vectors and double queues. For example, in a double queue A that holds 15 elements, A[7] points to the seventh element in the queue, and A[N] points to the N-th element in A. Table 10.3 demonstrates these methods.

2.Vectors

The vector is a variable length linear collection designed to provide rapid random access for objects. It is very similar to the static array, but vectors can change their storage size on demand at runtime.

Vectors are powerful containers. They can be used to implement queues, stacks, lists, or other complicated data structures.

a)Constructors and Destructors

There are four default constructors in a vector:

As in all containers in STL, vectors accommodate the object-oriented mechanism for memory management. All memory used by the vector itself, and the memory used by elements in the vector, are reclaimed by calling their destructors appropriately when the instance is out of scope.

One thing to notice is that removing an element from a vector just removes the element from the vector. It only calls its destructor but does not reclaim the memory used by the element.

b)Using Vector

1. Querying Status of Vector

There are four methods for querying the status of a vector: size(), max_size(), capacity(), and empty(). Here are their prototypes:

2. Adding Elements into a Vector

There are a number of ways to add an element into a vector: the constructor, push_back(), insert(), and swap() methods, the subscript operator, and the assignment operator. We have introduced some methods before. Here are the prototypes of push_back(), insert(), and swap():

The reverse() method reserves memory space to hold a certain number of elements. The reverse() method reallocates memory only if it finds its need is beyond the current capacity of the vector. Please notice that reallocation invalidates all references and iterators of a vector.

3. Deleting Elements from Vector

Programmers can use pop_back(), erase(), or clear() methods to remove an element from a vector. Here are their prototypes:

4. Accessing Elements in Vector

Methods front() and back() return the first and the last element in the vector respectively. The subscript operator can access the elements via the position directly.

c)Application

Example 10.1: Find all primes between 2 − N.

We now use a vector to rewrite the program in Example 9.4 and here is the program:

The result of the program is as follows:

3.Double Queues

A double queue is an extension of the queue data structure. Elements can be added or removed at both ends of double queues. Programmers can use the subscript operator “[]” to access an element directly.

a)Constructors and Destructors

There are four constructors in a double queue:

b)Using a Double Queue

1. Query the Status of a Double Queue

The size() method returns the number of elements in the double queue. The max_size() method returns the maximum number of elements that the double queue can hold. Method empty() tests whether the double queue is empty or not.

2. Adding Elements into the Double Queue

Adding elements into a double queue is very similar to adding them into a vector. Programmers can add elements via the constructor, push_back()/insert()/swap() methods, the subscript operator, and the assignment operator. Moreover, double queue provides an additional push_front() method to add an element at the head of the queue:

3. Removing Elements in the Double Queue

Methods pop_back()/pop_front()/erase()/clear() can remove elements in a double queue. Their semantics are the same as the ones for vectors. Moreover, double queues provide an additional method pop_front() for removing the first element of the double queue:

4. Access Elements

There are a number of ways to access elements in a double queue. Programmer can use methods like pop_front()/pop_back()/front()/back() to access an element in a queue. They can also use iterators to traverse the queue and access the element directly via subscript operator.

c)Application

Example 10.2: Store doubles with a double queue.

Results:

4.Lists

A list is a sequential container designed for adding/ removing elements quickly. It supports splicing, which adds all elements in a list to another one. Lists do not support random access, so some algorithms do not work on lists. Lists are implemented as double-linked lists in STL, so they can be traversed in both directions.

a)Constructors and Destructors

There are four constructors in a list:

b)Using Lists

1. Accessing the List

There are some minor differences between the interfaces of lists and vectors. There are no capacity()/subscript operators and at() methods, since lists do not support random access. Other interfaces are the same.

2. Adding Elements

The semantics of the method insert() are the same as the ones for vectors and queues but the insert() method in lists does not affect any iterators of a list. The splice() method can also add elements to a list:

3. Removing Elements

The erase() method removes both the element and the iterator pointing to it. The remove() method removes all elements equal to x in a list. Here is the prototype of remove():

c)Application

Example 10.3: Rewrite Example 9.7 with a list in STL.

Read 10 integers from the keyboard, create a linked list whose nodes hold these integers, and output the numbers in order. Then get another number from the keyboard, delete all nodes containing the number and output the data in the linked list. Clear the linked list before exiting.

Here is the program:

Results:

10.2.2Adapters of Containers

Adapters of containers do not implement the mechanism of storing the data and other functionalities like iterators. They just implement different interfaces based on the containers provided by STL.

1.Stacks

A stack is a linear collection with restricted access. Elements can only be added or removed from one end of a stack. Such operations are called push and pop respectively. In theory, there is no limit on the number of elements in a stack. In practice, a stack will overflow if it exceeds the memory limit of the computer, and it will underflow if one tries to pop an element from an empty stack. STL catches these exceptions to keep the program from crashing.

Stacks are based on the implementation of a certain container, which is specified at the declaration. Here is an example of declaration of a stack using a deque as its underlying implementation of storage:

Stacks only implement the primitives of the stack data structure. Methods push()/pop() add or remove an element at the top of stack, respectively. The method top() returns the reference of the element at the top of the stack. The method empty() checks whether a stack is empty. The method size() returns the number of elements in the stack. The underlying storage can be a vector, list, or deque, and the deque is the default implementation. Please refer to related documents like MSDN for detailed information. Here is a simple example of a stack.

Example 10.4: Initialize a stack holding integers.

Results:

2.Queues

Queue adapters provide interfaces for First In First Out (FIFO) services, i.e., adding an element to the tail of the container (enqueuing) and removing the first element of the container (dequeuing). Queues are implemented based on an underlying storage, typically a list or deque. The default underlying implementation of storage is the deque. Here is an example of a declaration of a queue, using the deque as the underlying storage implementation:

The method push() adds an element to the tail of the queue, the method pop() removes the first element of the queue, and the methods front() and back() return the reference of the head and the tail of the queue respectively. The method empty() checks whether the queue is empty, and the method size() returns the number of elements in the queue. Please refer to related documents like MSDN for detailed information. Here is a simple example of using a queue.

Example 10.5: Initialize a queue with integers.

Results:

Similar to stacks and queues, a priority queue is another adapter for implementing an advanced data structure. Priority queues are designed as ordered queues, which are able to extract the highest priority element from a queue.

10.3Iterators

Iterators are an object-oriented version of a pointer. They provide a uniform mechanism with which to traverse through a container. Pointers point to a memory location, so that program is able to access the object at the memory location through the pointer. You can see iterator as a type-safe pointer – it points to an object of a specific type.

The iterator is a common design pattern for bridging algorithms and containers. Real algorithms need to traverse a linear collection, like traversing a linked list through a pointer or traversing an array though subscripts. It is good to write an algorithm that works for all linear collections, so that you do not have to implement an algorithm many times for every type of linear collections. Therefore, the algorithm needs an abstraction layer to handle things like “get me the next element”, or “is it the end?”. In theory, pointers can handle this issue, but make the implementation of algorithms and containers more complicated. For an object-oriented pointer, an iterator is included in STL to enable writing abstract algorithms that works on all containers.

10.3.1Types of Iterators

There are five basic types of iterators: the input iterator, output iterator, forward iterator, bidirectional iterator, and random access iterator. There are two iterator adapters: the reverse iterator adapter and insert iterator adapter.

1.Categories

An input iterator can read data from a collection and an output iterator can write data to a collection.

A forward iterator is both an input iterator and output iterator, and it can traverse through the collection in a certain direction. A bidirectional iterator can traverse the collection in both directions. A random access iterator can access any position in a collection.

2.Iterator Adapters

An iterator adapter is an adapter that extends or adjusts the functions of an iterator. There are two types of iterator adapters:

The reverse iterator adapter reverses the functionalities of the increment operator and decrement operator, so that the algorithm processes the elements in a reversed order. All standard containers can be used with reverse iterators.

The insert iterator adapter overloads the assignment operator and turns it into insertions, so algorithms running on it inserts elements into the collections rather than overwriting the value of the elements. There are three types of insert iterators: back inserters, front inserters, and general inserters. Back inserters append an element to the end of a collection, and front inserters add an element at the head of a collection. General inserters decide the inserting position based on its parameters. Vectors, deques, links, and strings support back inserters, while deques supports front inserters, and all standard collections support general inserters.

Here is an example of an iterator adapter.

Example 10.6: Example of using iterator adapter.

Results:

10.3.2Auxiliary Functions in Iterators

There are three auxiliary functions in iterators: advance(), distance(), and iter_swap(). advance() and distance() emulate the power of random access iterators such as advancing and going back for several positions and calculating the distances of two iterates, through basic iterator primitives. Iter_swap() allows programmers to swap the values that the two iterators are pointing to.

1. advance() move the position of an iterator forward or backward by n position. Here is the prototype:

In bidirectional or random access iterators, n can be negative so that the iterator goes back for n elements.

2. distance() calculates the distances between two iterators:

pos1 and pos2 should point to the same container. If the iterators are not random access iterates, then pos1 must be able to reach pos2 via advancing pos1.

3. iter_swap() swaps the values that two iterators are pointing to:

pos1 and pos2 may point to different containers, but the elements they are pointing to must be able to be assigned to each other.

Here is an example:

Example 10.7: Example of auxiliary function of adapters.

Results:

10.4Algorithms in STL

STL implements generic algorithms via function templates. Generic algorithm gets an element with an input iterator, performs the operation with a function object, then outputs the result through an output iterator. The algorithm only specifies the semantics of the algorithm. Details of the implementation, like how to load/ store data from/ to containers are hidden in the implementation of iterators. The design splits functionalities into different components that are joined via a function template. Programmers are free to change any part of the whole link to implement new functionalities, such as working on a different type of container. That is the goal of generic programming.

There are four types of STL algorithms:Nonmutating Sequence Algorithms, Mutating Sequence Algorithms, sorting algorithms, and numerical algorithms. They cover a wide range of applications. This chapter focuses on the usage of these algorithms. The design and implementation of these algorithms are out of the scope of this chapter; please refer to advanced materials for more information.

10.4.1Using the Algorithms

We have used the copy algorithm in Section 10.3; here is the prototype of it:

The algorithm copies all elements in range [first, last) to [result, result + (last − first)), and it returns result + (last − first).

Here is the related code from Example 10.7:

Line 1 initializes an output stream iterator output and transfers all elements in col1 to the output iterator, which outputs the data to the console. Col1.begin() and col1.end() points to the first and the last element of col1 respectively.

Many algorithms take function objects as an input parameter for customizing the behaviors of the algorithms. Here are two prototypes of the sort algorithm:

The first prototype uses the operator < for comparison, so the result is in ascending order. The second prototype compares the elements with the function object comp, so that programmers can write their own rules for sorting, making the implementation flexible.

Here is an example of the sort algorithm:

Example 10.8: Example of sort algorithm.

Results:

10.4.2Nonmutating Sequence Algorithms

Nonmutating Sequence Algorithms do not modify the elements of the input containers, such as algorithms for finding an element in a container, checking whether two sequences are equal, and counting the occurrence of elements.

Tab. 10.4: List of Nonmutating Sequence Algorithms in STL.

Name Function
for_each Apply a function to each element in a sequence
find Find the first matching element in a sequence
find_if Find the first matching element in a sequence according to some conditions
adjacent_find Find two adjacent matching elements
find_first_of Return the first iterator in a matching sequence
find_end Return the last iterator in a matching sequence
count Count the number of matching elements
count_if Count the number of matching elements according some conditions
mismatch Find the first two mismatched elements
equal Test two sequences for equality
search Search for a sequence
search_n Search for a sequence that happens n times

Table 10.4 lists Nonmutating Sequence Algorithms in STL.

Here is an example of using Nonmutating Sequence Algorithms:

Example 10.9: An example of using Nonmutating Sequence Algorithms.

Results:

10.4.3Mutating Sequence Algorithms

Mutating Sequence Algorithms change the elements in the input containers. Table 10.5 lists Mutating Sequence Algorithms in STL.

Here is an example of a mutating algorithm.

Example 10.10: An example of mutating algorithm.

Tab. 10.5: Mutating Sequence Algorithms.

Name Function
copy Copy the elements in the interval
copy_n Copy first n elements in the interval
copy_backward Copy the elements in the interval in reverse order
fill Replace the elements in the interval with a specified value
fill_ n Replace first n elements in the interval with a specified value
generate Call the function object repeatedly, replace the elements in the interval with the return value
generate_n Call the function object repeatedly, replace first n elements in the interval with the return value
partition Put the elements that satisfy the condition by a function object before the other elements
stable_ partition Put the elements that satisfy the condition by a function object before the other elements, and keep the relative order
unique Remove the adjacent elements that have the same value, make the value unique
unique_copy Remove the adjacent elements that have the same value, make the value unique, and copy the result into another container
random_shuffle Shuffle the elements in the interval randomly
remove Remove all elements with the specified value in the interval
remove_ if Remove all elements that satisfy the condition in the interval
remove_ copy Remove all elements with the specified value in the interval and copy the result into another container
remove_ copy_if Remove all elements that satisfy the condition in the interval and copy the result into another container
replace Replace a certain kind of elements
replace_copy_if Replace a certain kind of elements conditionally and copy the result into another container
reverse Reverse the order of elements in the interval
reverse_ copy Reverse the order of elements in the interval and copy the result into another container
rotate Rotate two parts of elements in the interval
rotate_copy Rotate two parts of elements in the interval and copy the result into another container
swap Swap two elements using references
iter_swap Swap two elements using iterator
swap_ ranges Swap elements in two intervals
transform Make a new sequence using an existing sequence and a function object, or using two existing sequences and a function object

Results:

10.4.4Sorting Related Algorithms

There are a number of sorting related algorithms in STL, including sorting a sequence, merging two sorted sequences, setting algorithms, and heaping algorithms for sorted sequences. Here is an overview of these algorithms:

Four sorting algorithms: sort, partial_sort, partial_sort_copy, and stable_sort;

Four binary searching algorithms: binary_search, lower_bound, upper_bound, and equal_range;

Two algorithms of merging two sorted sequences: merge and inplace_merge;

Four algorithms to find maximum/minimum:

min, max, min_element and max_element;

Three sorting related algorithm:

lexicographical_compare, next_permutation, and prev_permutation;

Five set algorithms:

includes, set_union, set_ intersection, set_difference, and set_symmetric_difference;

Four heap algorithms: make_heap, pop_heap, push_heap, and sort_heap.

Algorithm nth_element resorts the sequence based on a particular rule. Table 10.6 lists sorting-related algorithms.

Tab. 10.6: STL Sorting-Related Algorithms.

Name Function
sort Sort the elements in the interval
stable_sort Sort the elements in the interval and keep the relative order of elements that have same values
partial_sort Sort the elements in the interval locally
partial_sort_copy Sort the elements in the interval locally and copy them to another place
nth_element Reorder the elements on the left side and the right side of the n-th element in the interval
upper_bound Find the elements that have a value equal to the specified value in the ordered interval [first, last) using binary search, return the last insertable iterator
lower_bound Find the elements that have a value equal to the specified value in the ordered interval [first, last) using binary search, return the first insertable iterator
binary_search Find the elements that have a value equal to the specified value in the ordered interval using binary search
equal_range Find the elements that have a value equal to the specified value in the ordered interval using binary search, return the result interval
merge Merge two ordered intervals and put the result into an interval that is not overlapped with the two inputted intervals
inplace_merge Merge two ordered intervals, replace the original interval with the result
min Return the minimum element
max Return the maximum element
min_element Return the position of minimum element
max_element Return the position of maximum element
lexicographical_compare Compare two intervals in lexicographical way
next_permutation Get the next permutation of the interval in lexicographical way
prev_permutation Get the previous permutation of the interval in lexicographical way
includes Check whether the elements of an interval are included in another interval
set_ union Return the union set of two sets using intervals. The set contains either the first set or the second set
set_difference Return the difference set of two sets using intervals. The elements in the result belong to the first set but do not belong to the second set
set_ intersection Return the intersection of two sets using intervals. The elements in the result belongs to the first set and the second set
set_symmetric_diffference Return the symmetric difference of two sets using intervals. The elements in the result only belong to one of two inputted sets
make_heap Reorder the elements in the interval, make it a heap
pop_heap Assume the interval is a heap, get the top of it
push_heap Assume the interval is a heap, insert a element into it
sort_heap Assume the interval is a heap, sort the elements in it

Here is an example of these algorithms:

Example 10.11: Example of sorting related algorithms.

Results:

10.4.5Numerical Algorithms

There are four generic numerical algorithms; Table 10.7 lists numerical algorithms in STL.

Here is an example of numerical algorithms in STL:

Example 10.12: An example of numerical algorithms.

Tab. 10.7: STL Numerical Algorithms.

Name Function
accumulate Calculate the sum of all elements in a sequence
partial_sum Add parts of the elements in a sequence, save them into another sequence
adjacent_difference Calculate the differences of adjacent elements in a sequence, save them into another sequence
inner_product Add the corresponding elements of two sequences (inner product of vectors)

Results:

10.5Function Objects

Many algorithms use a function object to abstract the operation of the algorithm, in order to provide additional flexibility. In this section, we introduce the design and usage of function objects in STL.

1.Function Objects

A function object is a design pattern used in STL for maximal flexibility. Function objects are an abstraction layer to hide the implementation detail. They behave like functions; they can return a value or perform some operations with side effects (like opening a file). In C++, all functions and any classes overloading the operator () can be used as function objects.

Now we use the numerical algorithm accumulate() as an example to discuss the design and usage of function objects. There are two prototypes of accumulate(): one uses the addition operator “+” to calculate the sum of the sequence, the other uses a customized function object to calculate the result.

Here is an example of using a function as a function object to implement a multiplication operation:

Example 10.13: Use a function as a function object.

Results:

Besides functions, function objects could be objects of a class that overloaded the operator doing the calling . The following example is another implementation of Example 10.13. In this example, the object of a class is used instead of a function.

Example 10.14: Define function objects using classes.

Results:

The class multclass overloads the operator () to implement the multiplication. The compiler generates an instance of multclass with its default constructor and passes it to the accumulate() algorithm. The programmer can create a customized instance to provide additional information for the algorithm.

Tab. 10.8: STL Function Objects in Standard Library.

STL Function Object Type Description
Plus<T> Numeric Input two operands with a type of T, return their sum
Minus<T> Numeric Input two operands with a type of T, return the result of subtracting the second operand from the first operand
Multiplies<T> Numeric Input two operands with a type of T, return their product
Divides <T> Numeric Input two operands with a type of T, return the result of dividing the second operand by the first operand
Modulus<T> Relation Input two operands x and y with a type of T, return result of x%y
Negate<T> Numeric Input an operand with a type of T, return its opposite number
Equal_to<T> Relation Input two operands x and y with a type of T, return true if x==y
Not_equal_to<T> Relation Input two operands x and y with a type of T, return true if x!=y
Greater<T> Relation Input two operands x and y with a type of T, return true if x>y
Less<T> Relation Input two operands x and y with a type of T, return true if x<y
Greater_equal<T> Relation Input two operands x and y with a type of T, return true if x>=y
Less_equal<T> Relation Input two operands x and y with a type of T, return true if x<=y
Logical_and<T> Logical Input two operands x and y with a type of T, return the result of logical and: x&&y
Logical_or<T> Logical Input two operands x and y with a type of T, return the result of logical or: x||y
Logical_not<T> Logical Input a operand x with a type of T, return the result of logical not: !x

STL defines a number of standard function objects, including arithmetic operations, boolean operations, and logical operations (as shown in Table 10.8). All of them are defined as inline functions to maximize the performance.

You can also use the multiplication function object provide by STL to implement the same functionalities. Here is the program:

Example 10.15: Using the STL function object multiplies to implement multiplication.

Everything is the same except we changed the multclass in Example 10.14 to multiplies<int>.

STL defines two basic classes of function objects, one for unary functions and one for binary functions for simplifying the design of function objects. For advanced topics please refer to related materials.

2.Function Adapters

Using function adapters is an easier way to create function objects. Interested readers might refer to advanced materials for details

10.6Application – Improving the HR Management Program of a Small Company

In this section, we demonstrate how to use STL’s vectors to simplify storing objects in the application.

There are three files in the program: employee.h, empfunc.cpp, and 10_16.cpp. 10_16.cpp is derived from Example 9.16.

Example 10.16: Improving the HR management program.

Results:

10.7Summary

This chapter introduced the concepts of containers, container iterators, iterators, iterator adapters, generic algorithms, function objects, and function adapters; and discussed a number of collections (vectors, deques, and lists) and container iterators (stacks, queues, priority queues). This chapter also discussed four generic algorithms, iterators, and function objects, and demonstrates how to use them.

STL is a powerful tool for simplifying the development of applications. Readers can refer to Appendix C of the student book for the prototypes of generic algorithms. Interested readers can refer to the MSDN of SGI’s documents to understand STL.

Exercises

10.1 In STL’s implementation of stacks, the method push() adds an element at the top of the stack, the method pop() removes an element from the top of the stack, the method empty() checks whether the stack is empty, the method top() return the top element of a non-empty stack, and the method size() returns the number of elements in the stacks. Please construct a stack with type int that calls the above functions to learn about the stack and the usage of these functions.

10.2 STL’s implementation of stack overload operators ==, !=, >, >=, <, <= for comparison between two stacks. Please construct two stack with type int and compare them with operator == and <. Observe the result.

10.3 In STL’s implementation of queues, the method push() adds an element at one end of the queue, the method pop() removes the last element from a nonempty queue, the method empty() checks whether the queue is empty, the method back()/front() returns the last element and the first element of a non-empty queue respectively, and the method size() returns the number of elements in the queue. Please construct two queues with type int and type char, respectively, and then apply the above methods to the queue to learn about the characteristics of the queue and the usage of these functions.

10.4 In STL’s implementation of deques, the method assign() reassigns values to a deque, themethod swap() swaps elements of two deque, and the methodbegin()/end() return the iterators pointing to the first/last element. Please construct a deque with int type and apply these functions to the deque to learn more about deques and the usage of these functions.

10.5 In STL’s implementation of deque, the methods front()/back() return the first/ last element of the deque. Please construct a deque with type char and apply these functions to the deque to learn more about deques and the usage of these functions.

10.6 In STL’s implementation of deque, method insert() inserts an element into a deque, the methods push_front()/pop_front() add /remove an element at the head of the deque, and push_back()/pop_ back() add /remove an element at the end of the deque. Please construct a deque with type char and apply these functions to the deque to lean more about the usage of these functions.

10.7 The length of a deque is variable. The method resize() resets the size of a deque, the method size() returns the number of elements in a deque, and the method max_size() returns the available size of the queue. Please construct a deque with type char and apply these functions to the deque to lean more about the usage of these functions.

10.8 What are the two types of containers? What are the five types of iterators? What are three types of container adapters? What mechanism does STL use to access the elements in containers?

10.9 Write a program to use the fill function in STL to set a certain range of container to a certain value, then use the generate function to generate a sequence of value.

10.10 Write a program to use swap(), iter_swap(), and swap_ranges() to swap elements in an array.

10.11 Write a program to use inplace_merge() to merge two sorted sequences in the same container, then use reverse_copy() to copy elements reversely into another container, and at last use unique_copy() to copy elements into another container uniquely.

10.12 Write a program to use set_difference() to find elements in the first sequence but not in the second sequence, then use set_intersection() to find elements occurring in both sequences, and at last use set_union() to find the union of the elements of both sequences.

10.13 Write a program to run count() and for_each() on a vector.

10.14 Write a program to run swap() and replace() on a vector.

10.15 Write a program to run sort(), binary_search(), merge(), and includes() on a vector to implement basic sorting, finding, merging, and set operations.

10.16 Write a program to run swap(), rotate(), partition(), erase(), fill(), and shuffle() on a collection.

10.17 Use heap-related algorithms to perform operations on a heap.

10.18 Use set-related algorithms to perform operations on a set.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset