Download presentation
Presentation is loading. Please wait.
1
Jim Fawcett Copyright © 1995 -2002
CSE687 – Object Oriented Design Class Notes Chapter 5 - Templates Jim Fawcett Copyright ©
2
Templates Templates are patterns used to generate code which differs only in the symbolic use of a type name. For example we might wish to define a function called max which returns the larger of its two arguments. Using conventional techniques we would write a max function for integers, one for doubles, and one for any other type for which we wanted a magnitude comparison. Templates allow us to write only one function and let the compiler generate code for all the types which need it. template <class T> T max(const T &t1, const T &t2){ return ((t2 > t1) ? t2 : t1); } We instantiate the function for some specific type by invoking it: int y = 1; int x = max(2,y); This process works beautifully as long as the type we instantiate has operations used to implement the function, in this case a magnitude comparison. Chapter 5 - Templates
3
Template Functions A function template is a declaration for a function which uses an unspecified type T as a formal parameter, return value, or local variable. template<class T> void swap(T &t1, T &t2) { T temp = t1; t1 = t2; t2 = temp; } Note: None of the processing in this example depends on the actual type represented by the generic parameter T. That’s a requirement for templates to succeed. Another requirement is that the generic type, T, possess the operations used in the function definition. In the swap function T is required to have an assignment operator=(...) and a copy constructor to build the temporary type T object temp, or compiler generated copy and assignment must be valid (only guaranteed for native types). If these conditions are not met compilation of the swap function may fail, or, if the compiler can generate them, the function’s operation may not be correct. Chapter 5 - Templates
4
///////////////////////////////////////////////////////////////
// swap.cpp - demonstrating function templates // #include <iostream.h> template <class T> void swap(T &t1, T &t2) { T temp = t1; t1 = t2; t2 = temp; } void main() { int x = 1, y = 2; double u = 2.5, v = -3.3; cout << "testing template based swap function\n\n"; cout << " integer test\n"; cout << " x = " << x << " and y = " << y << "\n"; cout << " swap(x,y)\n"; swap(x,y); cout << " x = " << x << " and y = " << y << "\n\n"; cout << " doubles test\n"; cout << " u = " << u << " and v = " << v << "\n"; cout << " swap(u,v)\n"; swap(u,v); testing template based swap function integer test x = 1 and y = 2 swap(x,y) x = 2 and y = 1 doubles test u = 2.5 and v = -3.3 swap(u,v) u = -3.3 and v = 2.5 Chapter 5 - Templates
5
Template Classes Template based classes are classes which depend on an unspecified type as a: formal parameter or return value for a member function local variable for a member function data attribute A template based class can be derived from a class which depends on a specific type by using the following rewrite rules: replace class className { ... } with template<class T> class className { ... } for each of the member functions replace returnType className::memName { ... } with template<class T> returnType className<T>::memName { ... } where returnType is either T or a specific type replace every object definition className obj; with className<type> obj; replace every occurrence of the specific type with the generic type name T A stack class is used in the next few pages as an example of use of these rewrite rules. The result is a generic stack class. Chapter 5 - Templates
6
Template Class Declaration
#ifndef TSTACK_H #define TSTACK_H // tstack.hpp - template stack class // modified from Effective C++, Scott Meyers #include <iostream> #include <cstdlib> template<class T> class stack { private: struct listNode { T data; listNode *next; listNode(const T& newdata, listNode *nextnode) // listNode constructor initializes node's data // and sets next ponter to point to next node : data(newdata), next(nextnode) { } }; listNode *top; public: stack(); // constructor ~stack(); // destructor void push(const T& object); // put elements on stack T pop(void); // remove elements from stack Chapter 5 - Templates
7
Template Member Functions in header file
template<class T> stack<T>::stack() : top(0) { } stack<T>::~stack(void) { while (top) { listNode *next_to_die = top; top = top->next; delete next_to_die; } void stack<T>::push(const T &object) { top = new listNode(object, top); if(!top) { std::cout << "\ndynamic memory exhausted\n"; exit(1); T stack<T>::pop(void) { static T safe; if (!top) { std::cout << "\nattempt to pop empty stack<T>\n"; return safe; listNode *save = top; int data = save->data; delete save; return data; #endif Chapter 5 - Templates
8
Template Instantiation
// tstack.cpp - template stack class // modified from Effective C++, Scott Meyers using namespace std; #include "tstack.h" //----< test stub > // template<class T> void print_field(T t) { cout.width(5); cout << t; } void main() { cout << "\nTesting Generic Stack Class\n\n"; stack<int> int_stack; int x=1, y=2, z=3; cout << "pushing " << x << " onto stack\n"; int_stack.push(x); cout << "pushing " << y << " onto stack\n"; int_stack.push(y); cout << "pushing " << z << " onto stack\n"; int_stack.push(z); cout << "popping stack: "; print_field(int_stack.pop()); cout << "\npopping stack: "; int_stack.pop(); cout << endl; Chapter 5 - Templates
9
Instantiation (continued)
stack<double> dbl_stack; double a=1.5, b=2.5, c=3.5; cout << "pushing " << a << " onto stack\n"; dbl_stack.push(a); cout << "pushing " << b << " onto stack\n"; dbl_stack.push(b); cout << "pushing " << c << " onto stack\n"; dbl_stack.push(c); cout << "popping stack: "; print_field(dbl_stack.pop()); cout << "\npopping stack: "; dbl_stack.pop(); } Testing Generic Stack Class pushing 1 onto stack pushing 2 onto stack pushing 3 onto stack popping stack: 3 popping stack: 2 popping stack: 1 popping stack: attempt to pop empty stack<T> Chapter 5 - Templates
10
Instantiation (continued)
pushing 1.5 onto stack pushing 2.5 onto stack pushing 3.5 onto stack popping stack: 3 popping stack: 2 popping stack: 1 popping stack: attempt to pop empty stack<T> Chapter 5 - Templates
11
Template Design Notes A template based class places some requirements on specific types used for instantiation: If a generic class is to compile successfully for a specific type, either intrinsic or user defined, that type must support all the operations applied to it in the bodies of the generic class member functions. For example, if a generic class uses a comparison operator, say operator<(), in one or more of its member function bodies, then that operator must be defined for any type which is used to instantiate the class. That will certainly be true for the primitive arithmetic types, but it may not be true for a user defined type. The result in that case is a compilation failure. A more serious error occurs when the missing operation is one of those that can be supplied by the compiler but the compiler version is not correct for the class semantics. In that case the template instantiation will compile but give erroneous results. Templates can be specialized by defining the form of an instantiation for some specific type. By doing this we can make the template behave differently for that specific type (or any number of specified types). Consider for example the function template example given on the next page. we provide an example specialization for the int type. Chapter 5 - Templates
12
Template Specializations
If we instantiate the max function for C strings, it fails to operate as expected. char s[17] = “this is a string”; char t[15] = “another string”; char *bigger = max(s,t); This failure happens because the built in comparison operator “>“ for pointers compares memory locations, not lexicographic ordering. We can take care of this problem by specializing the max function for C strings, using the strcmp() function from the standard C library. char* max(char *s1, char *s2) { return ((strcmp(s1,s2)>0) ? s1 : s2); } Now the max() function will operate correctly for ints, floats, and doubles which all have magnitude operators and for C strings due to the specialization. Of course, compilation failures will occur for any type which does not have the comparison operator unless we provide specializations for that type also. Chapter 5 - Templates
13
A Specialization Example
// functmpl.cpp -- example function template #include <iostream.h> template<class T> T fun(T& t) { cout << "inside fun(t) value of t is " << t << endl; return t; } int fun(int& t) { cout << "inside fun(t) value of t is the integer " void main() { int x = 1; double y = 2.75; char z = 'z'; cout << "Testing Function Template\n\n"; cout << "fun(" << x << ") returns " << fun(x) << endl; cout << "fun(" << y << ") returns " << fun(y) << endl; cout << "fun(" << z << ") returns " << fun(z) << endl; Testing Function Template inside fun(t) value of t is the integer 1 fun(1) returns 1 inside fun(t) value of t is 2.75 fun(2.75) returns 2.75 inside fun(t) value of t is z fun(z) returns z Chapter 5 - Templates
14
Containers Containers are classes which implement data structures containing objects of another class Examples: stacks, queues, lists, vectors, arrays... Often the logic necessary to manage a container is independent of its contents, using the contained type only symbolically. When this is the case containers may be implemented with templates to yield a generic container. If an operation on the generic type is needed by the container, say assignment or comparison, then the type used to instantiate the container must provide it. Otherwise compilation will fail. Example: if we expect to support binary search on a generic array of objects: array<T> myArray; then the type T will need to support assignment and comparison so we can sort the array elements. Chapter 5 - Templates
15
Container Types Containers are classified as homogeneous or heterogeneous. Homogeneous containers hold only one type of object. They generalize an array of primitive types. Heterogeneous containers hold more than one type of object, Usually all belong to a common derivation hierarchy. If container expects a reference to a base class object it will accept a reference to any type which is publicly derived from the base class. Upward type compatibility is the basis for flexible run-time creation and management of objects. Heterogeneous containers occur frequently in fully object oriented designs. If we implement a container with templates, it will be homogeneous if we instantiate it with a class with no derivations or with a primitive type, e.g.: array<char> myArray; If, however, we instantiate the container with pointers to the base class of a derivation hierarchy, then the same design yields a heterogeneous container, e.g.: array<myBaseClass*> myArray; Chapter 5 - Templates
16
Container Semantics Containers are said to have either:
value semantics, e.g., objects are copied into the container reference semantics, e.g., the container is passed a reference or pointer to an external object. Reference Semantics: container holds references to objects, e.g., pointer, c++ reference, or handle an object can be “in” more than one container simultaneously insertion does no copying when containers are destroyed their objects are not destroyed Problems: if a contained object is destroyed before its container a dangling reference results changing a value “in” a container may affect other clients of the object Chapter 5 - Templates
17
Container Semantics (cont)
Value semantics: container holds the objects themselves no object lives in more than one container objects are copied into the container, so the container owns its objects when the container is destroyed the objects are also destroyed. It is easy to simulate reference semantics by using containers designed with value semantics but holding reference objects, e.g.: array<myObjects*> list_of_pointers The moral of this story is that one should always design containers with value semantics. If the client wants reference semantics that’s easy to arrange by using reference objects. The client then takes responsibility to manage the reference semantics properly. Chapter 5 - Templates
18
Container Iterators Iterators are objects which are designed to traverse a container. Often the container must grant the iterator friendship status or provide member accessor functions designed to allow the iterator to do its job. A container/iterator class pair generalizes the operations of an array/pointer pair for primitive types. The iterator constructor makes the iterator “point” to an object in the container. The iterator has the intelligence and access necessary to traverse the container, object-by-object. Chapter 5 - Templates
19
Container Iterators Issues:
Either the designer or client must take great care not to destroy any container object if an iterator is looking at it. The result would be a dangling reference. The designer could take care of this problem by allowing only the iterator to remove container contents. Presumably the iterator would then possess logic to avoid the dangling reference. This, however, may not fit very well with the logical model for the container. Iterators are especially useful in cases where a container may need to have more than one simultaneous traversal. This is useful when implementing graph algorithms, for example. Another situation where iterators are useful is where there may be more than one type of traversal process, say depth first search versus breadth first search. Then an iterator can be designed for each process. Chapter 5 - Templates
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.