390 likes | 587 Views
EE4E. C++ Programming. Lecture 5 Templates and the Standard Template Library (STL). Contents. Introduction Function templates Class templates Creating function templates from class templates The Standard Template Library (STL). Introduction. Templates are a powerful feature of C++
E N D
EE4E. C++ Programming Lecture 5 Templates and the Standard Template Library (STL)
Contents • Introduction • Function templates • Class templates • Creating function templates from class templates • The Standard Template Library (STL)
Introduction • Templates are a powerful feature of C++ • They allow the programmer to specify with a single code segment an entire range of related functions or classes • For example we might write a sort() function template able to sort ints, floats, chars and programmer defined classes • We might write a linked list class template able to handle many different data types • The Standard Template Library is a sophisticated set of class templates enabling us to use generalized container classes
Function templates • Function templates are useful if we want to perform identical operations on different types of data • Examples would include searching and sorting arrays of different data types • The algorithms used would be the same for all data types • Only object equality and relational tests would differ
Function template definitions begin with the template keyword • A list of formal type parameters follows in angle (< and >) brackets • template <class T>function_name • template <class A, class B> function_name • The function template is instantiated by specifying actual types (which can be primitive types or user defined types) in place of the formal types
Example • A function template printArray() that prints the contents of an array of any instantiated type template class<T> void printArray(const T* array, const int count) { for (int i=0; i<count; i++) { cout << array[i] << “ “; cout << “\n”; } }
void main() { const int n=5; int intArray[n]={1,2,3,4,5}; float floatArray[n]={1.0,2.0,3.0,4.0,5.0}; char charArray[n]=“Hiya”; printArray(intArray,n); printArray(floatArray,n); printArray(charArray,n); }
The compiler in this case instantiates the following printArray() functions • printArray(const int*, const int) • printArray(const float*, const int) • printArray(const char*, const int) • No runtime overhead is involved • If T is replaced by a programmer defined type (such as Date), then the complier will look for a overloaded operator<<() function
void main() { const int n=5; Date d(1,1,1990); Date* DateArray=new Date[n]; for (int i=0; i<n; i++) DateArray[i]=d++; // operator<<(ostream& os, const Date& d) called here printArray(DateArray,n); }
Class templates • Class templates enable reusable data types to be easily created • Often called generic programming • For example we can create a generic stack (a last in first out data structure) using class templates • The generic stack can be instantiated with any data type • Stack of integers, stack of doubles, stack of dates etc
// Stack class template template <class T> class Stack { private: int size; // No. of elements in the stack int top; // Location of the top element T* stackPtr; // Pointer to the stack public: Stack(int); ~Stack() { delete[] stackPtr; } int push(const T&); // Push an element int pop(T&); // Pop an element int isEmpty() { return top==-1;} int isFull() { return top==size-1;} };
template<class T> Stack<T>::Stack(int a) { size=a; top=-1; // Initially empty stackPtr=new T[size]; // Allocate memory } template<class T> int Stack<T>::push(T& val) { if (!isFull()) { stackPtr[++top]=val; return 1; } return 0; } template<class T> int Stack<T>::pop(T& val) { if (!isEmpty()) { val=stackPtr[top--]; return 1; } return 0; }
void main() { Stack<char> charStack(7); char[] name=“Michael”; int j=0; while(charStack.push(name[j]) j++; char c; cout << “My name in reverse is : “; while(charStack.pop(c)) cout << c; }
This code assumes that an overloaded assignment operator function exist for programmer defined data types • This must be provided if the object used with the Stackclass contains dynamically allocated memory
Creating function templates from class templates • We can easily create a function template by passing a class template as an argument to a function • A classic example is a sort() function template which sorts the contents of a container template (for example and array) passed as an argument
template <class T> class Vector { private: T* v; int size; public: Vector(int) ; // Constructor int getSize( ) { return size; } T& operator[] (int ); // overloaded [] }; • We can demonstrate this by first creating a simple Vector template (like an array)
template<class T> void sort(Vector<T>& v) { // sort the elements of v into increasing // order using a bubble sort int size=v.getSize(); for (int i=0; i<size-1; i++) for (int j=size; i<j; j--) if (v[j]<v[j-1]) // swap v[j] & v[i] { T t = v[j]; v[j]=v[j-1]; v[j-1] = t; } } • We can then create a sort() function template
void main() { Vector<int>& vi(100); Vector<char*>& vc(100); Vector<float>& vf(100); Vector<Circle>& vci(100); // initialize the vectors // ….. . sort(vi); sort(vc); sort(vf); sort(vci); }
The compiler will try and create the correct sort() functions by instantiating the templated functions • It will run into 2 problems in this example • Instantiating sort() with a char* (a string) argument will produce incorrect results • The ‘<‘ operator will not test for alphabetical ordering of the strings • Instantiating sort() with a Circle object argument will produce a compilation error • We need an overloaded operator<() function defined on Circle objects
We can create sort() functions for specific datatypes that will override the instantiated function template<class T> void sort(Vector<char*>& v) { // sort the elements of v into increasing // order using a bubble sort int size=v.getSize(); for (int i=0; i<size()-1; i++) for (int j=size()-1; i<j; j--) if (strcmp(v[j],v[j-1])<0) { T t = v[j]; v[j]=v[j-1]; v[j-1] = t; } }
We can also create an operator<() function for Circle objects int operator < (Circle& c1,Circel& c2) { return c1.radius<c2.radius; }
Other features of templates • Templates and friends • With templates, friendship can be established between either a class template or a particular instantiation of the template and global/member functions or entire classes • A few examples will clarify this:
template<class T> class X { …. friend void f(); …. } • Declare a function f() to be a friend of every class specialization of class template X • Declare every member function of class Y to be a friend of every class specialization of class template X template<class T> class X { …. friend class Y; …. }
Declare a function f(X<T>) to be a friend of class specialization X<T> for any T (such as float) • Declare every member function of class specialization Y<T> to be a friend of class specialization X<T> for any T (such as float) template<class T> class X { …. friend void f(X<T> &); …. } template<class T> class X { …. friend class Y<T>; …. }
The Standard Template Library (STL) • The Standard Template Library (STL) is an addition to the C++ Standard Library • Developed at the Hewlett-Packard Labs in the mid-late 90’s • STL defines powerful, template-based, reuseable components that implement many common data structures and algorithms to process those data structures • STL is extremely complex, being optimised for efficiency. We will only scratch the surface
STL is based on 3 components • Containers • Iterators • Algorithms • Template-based containers are data structures used to group objects of any type • Examples include linked lists, vectors and sets • Iterators encapsulate the mechanisms used to access container elements • A simple pointer can be used as an iterator for a standard array
Algorithms are functions that perform common manipulations on containers • Examples include searching and sorting algorithms • There are around 70 algorithms implemented in the STL • A key aspect of the STL is that an algorithm can be applied to a container without regard to the exact implementation of the container • The container’s iterator just has to support the minimum requirement of the algorithm • This means that programmers can write one algorithm which can be applied to multiple containers
Containers • Containers divided into 3 main categories • Sequence containers • Essentially linear data structures • Associative containers • Can be searched quickly using name/value pairings • Container adapters • Stacks, queues and priority queues
Examples of sequence STL containers include • vector • Provides random access with constant time insertions and deletions at the end • deque (double entry queue) • Provides random access with constant time insertions and deletions at the beginning and the end • list • Provides linear time access but constant time insertions and deletions at any position in the list
Examples of associative STL containers include • set • Provides rapid lookup (set membership), no duplicates around • multiset • Asabove but allows duplicates
Iterators • Iterators allow generic algorithms to ‘plug in’ to different containers • As long as the container supports the iterator that the algorithm uses to access container elements, the algorithm can be applied to it
Container Iterator Algorithm vector sort find list
There are different classes of iterator to support things like linear or random access • However, certain iterator operations are uniform and can be applied to all containers • * (de-referencing) allows access to the element to pointer to by the iterator • ++ (increment) moves the iterator to the next element • begin() points the iterator at the first element • end() points the iterator at the last element
Generally speaking, we have the following sub-division of iterators and associated containers • Sequence containers vector, deque • Random access iterators • Sequence container list • Linear bidirectional iterator • Associative containers set, multi-set, map, multi-map • Linear bidirectional iterator • No iterators supported for stack, queue, priority queue
Iterator operations • Depends on iterator type • A few examples • ++p, p++,--p, p--(pre and post increment/decrement) • Applies to bidirectional and random access • *p (de-referencing) • Applies to bidirectional and random access • p==p1(test for equality) • Applies to bidirectional and random access
p+=i, p-=i (increment/decrement iterator p by i positions) • Applies to random access only • p[i] (returns a reference to the element offset from p by i positions) • Applies to random access only • p<=p1(returns true if iterator p less than iterator p1 (ie it is before p1 in the container) • Applies to random access only
Example. vector • Enables efficient random access rather like an array but can re-size itself • Supports a random access iterator • All STL algorithms can operate on a vector as they all support random access • A vector has a size (the actual number of elements in the container) and a capacity (the number of elements that can be added before the vector resizes)
#include <vector> int main() { int array[6]={1,2,3,4,5,6}; vector<int> vec(array,array+6); for (int *ptr=array; ptr!=(array+6); ptr++) cout<<*ptr; vector<T>::iterator it; for (it=vec.begin(); it!=vec.end(); it++) cout << *it; // sort the vector sort(vec.begin(),vec.end()); }
The previous program shows the similarity between array access using pointers and container access using an iterator • Also de-referening an iterator (pointer) to access the container (array) element • Also the program shows how a sort() function is called • Iterators passed as arguments • In general the STL is enormously powerful and should always be used in preference to custom written container classes