Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Templates/Generics in C++ and Java By: Dariusz Gawdzik 1 Some Motivation Suppose you want to develop a general container such as stack in C++ or Java. One option is to build one container for a specific data type (i.e.. One for integers, one for doubles, one for booleans ). The following is the implementation of stack that holds integers (parts in red font are due to our choice of the stack’s data type on which it operates ). 2 IntegerStack.h #pragma once class IntegerStack { public: IntegerStack(int capacity); ~IntegerStack(void); void Push(int i); int Pop(); int Peek() const; bool Empty() const; bool Full() const; void Print() const; private: int top; int capacity; int *array; }; 3 IntegerStack.cpp #include "integerstack.h" IntegerStack::IntegerStack(int capacity) { assert(capacity > 0); top = 0; this->capacity = capacity; array = new int[capacity]; assert(Empty()); } IntegerStack::~IntegerStack(void) { delete [] array; } bool IntegerStack::Empty() const{ return top == 0; } 4 IntegerStack.cpp (continued ) bool IntegerStack::Full() const{ return top == capacity; } int IntegerStack::Peek() const{ return array[top-1]; } void IntegerStack::Push(int i) { assert(! Full()); array[top++] = i; assert( ! Empty() ); assert( Peek() == i ); } int IntegerStack::Pop(){ assert(! Empty()); return array[--top]; } 5 How About Stack of Doubles? We can just modify the stack we already built for integers changing the definitions in red on previous slides from int to double. 6 How About Stack of Doubles? Doing it this way would create a lot of stacks one for every data type. Not really a good solution especially if in the future we want to modify the way the stack is implemented. This is a violation of single choice principle which states that a software system that supports a set of alternatives should limit the knowledge of the alternatives to only one module. 7 How About Stack of Doubles? But we can do better in C++. We can create a stack that operates on a structure that has as one of its variables union of all the primitive data types available in C++. Also, as one of the fields of the union we add a variable of type void * (this way we can insert any pointer into the structure without explicit cast). So, in fact, we can insert any object we care to create. This is what we want, at least at first sight. 8 GenericStack.h #pragma once enum type {INTEGER, CHARACTER, BOOLEAN, DOUBLE_PRECISION, SINGLE_PRECISION, VOID_POINTER }; struct item { type data_type; union{ int integer; //more primitive data types ommited... char character; bool boolean; double double_precision; float single_precision; void *void_pointer; }data; }; 9 GenericStack.h (continued ) class GenericStack { public: GenericStack(int capacity); ~GenericStack(void); void Push(item i); item Pop(); item Peek() const; bool Empty() const; bool Full() const; void Print() const; friend ostream & operator << (ostream &, const item &); private: int top; int capacity; item *array; }; 10 GenericStack.cpp #include "genericstack.h" GenericStack::GenericStack(int capacity) { assert(capacity > 0); top = 0; this->capacity = capacity; array = new item[capacity]; assert(Empty()); } GenericStack::~GenericStack(void) { delete [] array; } 11 GenericStack.cpp ( continued ) bool GenericStack::Empty()const{ return top == 0; } bool GenericStack::Full() const{ return top == capacity; } item GenericStack::Peek() const{ return array[top-1]; } 12 GenericStack.cpp ( continued ) void GenericStack::Push(item i) { assert(! Full()); array[top++] = i; assert( ! Empty() ); } item GenericStack::Pop(){ assert(! Empty()); return array[--top]; } void GenericStack::Print() const{ cout << "Generic Stack content: " << endl; cout << "|-----------------|"<<endl; for(int i = top-1; i >= 0; i--){ cout << " " << array[i] <<" " << endl; cout << "|-----------------|"<<endl; } } 13 GenericStack.cpp (continued ) ostream & operator << (ostream & os, const item & x) { switch (x.data_type ) { case BOOLEAN : return os <<x.data.boolean; break; case INTEGER: return os <<x.data.integer; break; case CHARACTER: return os <<x.data.character; break; case DOUBLE_PRECISION: return os <<x.data.double_precision; break; case SINGLE_PRECISION: return os <<x.data.single_precision; break; case VOID_POINTER: return os <<x.data.void_pointer; break; default: return os << "unknown data type"; } } 14 Is the GenericStack the Best We Can Do in C++? The generic stack seems to solve our problem. After all, we have only one stack for all of the data types and the single choice principle is embraced. But in C++ we can implement a generic stack using templates. Templates are basically classes that require parameter(s) in a similar fashion like functions. 15 Templates in C++ To take the metaphor a little further. Classes that are not templates are like functions that do not take any parameters. The parameter for template is provided at a time the template is instantiated. The parameter resolves ambiguity as to what data type the class/template operates on. This is best understood by using an example (how about stack ). 16 TemplateStack.h template <class G> class TemplateStack { public: TemplateStack(int capacity); ~TemplateStack(void); void Push(G i); G Pop(); G Peek() const; bool Empty() const; bool Full() const; void Print() const; private: int top; int capacity; G *array; }; #include "TemplateStack.cpp" 17 TemplateStack.cpp #include <iostream.h> #include <assert.h> template <class G> TemplateStack<G>::TemplateStack(int capacity) { assert(capacity > 0); top = 0; this->capacity = capacity; array = new G[capacity]; assert(Empty()); } template <class G> TemplateStack<G>::~TemplateStack(void) { delete [] array; } template <class G> bool TemplateStack<G>::Empty()const{ return top == 0; } 18 TemplateStack.cpp ( continued ) template <class G> bool TemplateStack<G>::Full() const{ return top == capacity; } template <class G> G TemplateStack<G>::Peek() const{ return array[top-1]; } template <class G> void TemplateStack<G>::Push(G i) { assert(! Full()); array[top++] = i; assert( ! Empty() ); } 19 TemplateStack.cpp (continued ) template <class G> G TemplateStack<G>::Pop(){ assert(! Empty()); return array[--top]; } template <class G> void TemplateStack<G>::Print() const{ cout << "Generic Stack content: " << endl; cout << "|-----------------|"<<endl; for(int i = top-1; i >= 0; i--){ cout << " " << array[i] <<" " << endl; cout << "|-----------------|"<<endl; } } 20 The syntax of template Declaration The class declaration in .h file is preceded with keyword: template <class G> The alternate keyword supported by some compilers is template <typedef G> 21 The G is the formal generic parameter in which place we will put a data type when we instantiate the class. The class keyword in a misnomer the actual parameter need not be a class but can in fact be a primitive type as well; hence the better name typedef supported by some compilers. We need to place the #include “TemplateStack.cpp” At the end of TemplateStack.h file. 22 In our class we put the generic parameter G where we regularly would put a data type on which stack operates. We can instantiate the template stack as we would a normal class except we provide the generic parameter. 23 #include <stdio.h> #include "TemplateStack.h" int main(int argc, char* argv[]) { TemplateStack<double> *tStack = new TemplateStack<double>(20); tStack->Push(13.2); tStack->Push(2.0); tStack->Push(1.12); tStack->Print(); TemplateStack<char *> *chStack = new TemplateStack<char *>(20); chStack->Push("nice"); chStack->Push("are"); chStack->Push("templates"); chStack->Print(); return 0; } 24 Script started on Mon Mar 22 16:41:02 2004 2;red:/cs/home/cs973269/cs4301/A3/C++/build 301 build % main Generic Stack content: |-----------------| 1.12 |-----------------| 2 |-----------------| 13.2 |-----------------| Generic Stack content: |-----------------| templates |-----------------| are |-----------------| nice |-----------------| 302 build % exit exit script done on Mon Mar 22 16:41:11 2004 25 How about Java? If you think that Java is a much better language than C++ you may say that all the complexities of templates can be avoided, if as in Java, we have a class hierarchy where Object is the ultimate ancestor of all classes. In Java collections such as stack are implemented to operate on Objects. 26 Java’s Approach Let’s have a look at Java collections and see if we can find some problems with this approach. First of all, we can only insert Objects into Java collections which leaves primitive types and arrays out of the loop. 27 Java’s Approach Yes, we can use wrapper classes for primitive types but this provides for slower executing programs and gives programmers another thing to take care of. But the biggest problem is with static typing. Let me illustrate what I mean by the previous statement. 28 import java.util.Stack; class Main { public static void main(String[] args) { Stack st = new Stack(); st.push(new Integer(1)); st.push(new String("good")); Integer i = (Integer) st.pop(); System.out.println(st); } } % java Main Exception in thread "main" java.lang.ClassCastException at Main.main(Main.java:12) 29 Java and Static typing The pervious program had a semantic bug in it. I suppose the programmer wanted to use the stack for integers and by mistake inserted the string. The exception was raised by JVM at run time and, in this case, it caused the entire system to crush. 30 Java and static typing In this case no damage done, but imagine you are writing application for auto pilot in an air plane. You don’t want this kind of semantic bug to crush the plane nor do you want to provide for any imaginable possibility in your exception handler. 31 Java and static typing The fact is, the semantic bugs are the hardest to find and to recover from. On many occasions you will not be able to provided for recovery in the exception handler. Ideally we would want this kind of bug to be caught at compile time. 32 Java versus C++ Java is an improvement over C++ in many respects but not in all respects unfortunately. The following code is the equivalent of Java code that caused our system to crush. It uses our TemplateStack. 33 #include "TemplateStack.h" int main(int argc, char* argv[]) { //testing template stack TemplateStack<int> *iStack = new TemplateStack<int>(20); iStack->Push(1); iStack->Push(2); iStack->Push("some string"); iStack->Print(); return 0; } ../prog/Main.cpp: In function `int main(int, char **)': ../prog/Main.cpp:17: no matching function for call to `TemplateStack<int>::Push (char[12])' ../lib/TemplateStack.cpp:41: candidates are: void TemplateStack<int>::Push(int) make: *** [Main.o] Error 1 34 Java versus C++ So the error that caused our system to crush miserably in Java was detected in C++ through the use of template class at compile time (before we released the auto pilot module that caused the plane to crush and us to get fired ). Can we do anything to correct that Java deficiency? 35 Java and templates According to Sun Microsystems Inc. adding Generics (templates) to Java is one of the most requested extensions to the language: “This feature is one of the most frequently requested language extensions on the JDC (no. 7 on the bug parade - no. 2 among language extensions). “ (http://java.sun.com/developer/techDocs/New sletters/2002/nl1113.html) 36 Java 1.5 With Java 1.5, still in Beta version, we get the support for Generics. Lets have a look at the basic syntax which is somewhat similar to the syntax of C++. 37 public class GenericStack<G> { /* Public Part */ GenericStack(int capacity){ assert(capacity > 0); top = 0; this.capacity = capacity; array = new Object[capacity]; assert(Empty()); } public boolean Empty(){ return top == 0; } public boolean Full() { return top == capacity; } public G Peek() { return (G) array[top-1]; } 38 public void Push(G i) { assert(! Full()); array[top++] = i; assert( ! Empty() ); } public G Pop(){ assert(! Empty()); return (G) array[--top]; } public void Print(){ System.out.println("Generic Stack content: "); System.out.println("|-----------------|"); for(int i = top-1; i >= 0; i--){ System.out.println(" " + array[i] +" System.out.println("|-----------------|"); } } /* Private Part */ private int top; private int capacity; private Object[] array; } "); 39 Some points First the java syntax is shorter we just need to add <G> to the class definition. There is some deficiency of implementation: we can’t define G[] array like we could in C++. This results in defining array of Objects for private implementation and then casting to (G) whenever we return value from that array. When compiling we need to add –source 1.5 switch to javac otherwise the new syntax will create compile time errors. 40 class Main { public static void main(String[] args) { GenericStack<Integer> st = new GenericStack<Integer>(20); st.Push(1); st.Push(2); st.Push(3); st.Print(); GenericStack<String> s = new GenericStack<String>(20); s.Push("Java 1.5"); s.Push("in"); s.Push("Generics"); s.Print(); } } 41 Generic Stack content: |-----------------| 3 |-----------------| 2 |-----------------| 1 |-----------------| Generic Stack content: |-----------------| Generics |-----------------| in |-----------------| Java 1.5 |-----------------| 42 Java and generics The introduction of generics allows as to define homogeneous containers of which until now array was the only representative. But what happens when we want to define a container that is not homogeneous. 43 Java and Generics Of course there is a simple solution create Stack<Object> How about inheritance and generics can we do this: List<String> s_list = new ArrayList<String>(); List<Object> o_list = s_list; // is a list of string a // list of object? 44 Java and Generics As it turns out we can’t the compiler will issue incompatible types error. If we could then this code would generate error as well. o_list.add(new Object()); String s = o_list.get(0); 45 Java and Generics What if we want to specify that our method takes a stack that has a generic actual parameter that can be a base or derived class of certain type (doing Stack<SomeType> will not work as you saw previously since Stack<SomeType> is not a parent of Stack<Child of SomeType> ) void method( Stack<“what goes here?”> arg) {….} 46 Java and Generics Two solutions: Define it as: void method(Stack<? extends Type> arg ){..} Define it as void method(Stack<?> arg){...} where ? Is a wild card which specifies any type (so method can operate on any stack what so ever). 47 Java 1.5 Java 1.5 to be soon released makes major changes to the language. Besides adding generics it introduces boxing/unboxing for primitive types so that we no longer have to have wrapper classes. It introduces new for loop construct It introduces variable arguments to the functions. It finally introduces enum data type. 48 Short preview Variable arguments //args is of type Object[] void argtest(Object ... args) { for (int i=0;i <args.length; i++) { System.out.println(args[i] ); } } argtest("test", "data"); 49 Short preview Enumerations public enum StopLight { red, amber, green }; 50 Short preview New for loop for iterators Before: ArrayList<Integer> list = new ArrayList<Integer>(); for (Iterator i = list.iterator(); i.hasNext();) { Integer value=(Integer)i.next(); } After: ArrayList<Integer> list = new ArrayList<Integer>(); for (Integer i : list) { Integer value=(Integer)i.next(); } 51 Some Theory Behind Generics Generics allow to abstract over types. In object oriented programming class is viewed as a merge of the notion of module and type. So, if we don’t have genericity then every class is a module that is also a type. With generics a class defined as Stack<G> is a type pattern covering an infinite set of possible types which are obtained by providing the actual generic parameter G. 52 References: http://java.sun.com/j2se/1.5/pdf/Tigerlang.pdf Object-Oriented Software Construction (2nd ed) by Bertrand Meyer. (Bible for OOP). http://java.sun.com/j2se/1.5/pdf/generic s-tutorial.pdf 53 Thank You The End. 54