Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 108 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
108
Dung lượng
2,04 MB
Nội dung
file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm to be consistent across different functions of the template class. Here is another stack function where I use different names for the type parameter and for the expression parameter. template <class T, int s> // different names for parameters void Stack<T,s>::push (const T& c) // consistent parameter names { if (top < size) // normal case: push symbol items[top++] = c; else { T *p = new T[size*2]; // get more heap memory if (p == 0) { cout << "Out of memory\n"; exit(1); } for (int i=0; i < size; i++) // copy existing stack p[i] = items[i]; delete [] items; // return heap memory items = p; size *= 2; // update stack size cout << "New size: " << size << endl; items[top++] = c; } } // push symbol on top Similar to type parameters, expression parameters have to be listed both in the template parameter list and in the class name prefix for all member functions. Even if an expression parameter is not used within the body of the function, it still has to be listed. template <class Type, int sz> Type Stack<Type,sz>::pop() // parameters are not used { return items[¡Xtop]; } template <class Tp, int s> bool Stack<Tp,s>::isEmpty() const // parameters are not used { return top == 0; } template <class Type, int sz> Stack<Type,sz>::~Stack() { delete [] items; } // parameters are not used The major characteristic of template classes with expression parameters is that each instantiation represents a different C++ type. As different types, they are not compatible, and an object of one type cannot be used where an object of another type is expected. Stack<int,4> s; // stack object file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1081 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Stack<int,8> s1; // incompatible stack object Consider, for example, a global function DebugPrint(). It has the parameter of class Stack<int,4>. Notice that the parameter object is passed by reference¡Xthe private declaration of the Stack<Type,sz> copy constructor prevents passing stack objects by value. Also notice that the parameter is not labeled as const because it changes (even temporarily) during function execution. void DebugPrint(Stack<int,4>& s) // no const modifier { Stack<int,4> temp; cout << "Debugging print: "; while (!s.isEmpty()) // pop until stack is empty { int x = s.pop(); temp.push(x); // save in temporary stack cout << x << " "; } // print each component cout << endl; while (!temp.isEmpty()) // pop until stack is empty { s.push(temp.pop()); } } // restore initial state The stack objects s and s1, are of different types. Object s can be passed as a parameter to DebugPrint(). An attempt to do so with object s1 causes a syntax error. DebugPrint(s); // OK DebugPrint(s1); // syntax error Actual expressions that evaluate to the same value are equivalent. const int length = 4; Stack<int,length> s2; // compatible with Stack<int,4> As far as templates with type parameters only are concerned, all instantiations with the same actual type arguments are of the same type, and one object can be used instead of another object. Here we reconsider the template class with one type parameter. template <class Type> class Stack { file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1082 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Type *items; // stack of items of type Type int top, size; // current top, total size Stack(const Stack& = 100); operator = (const Stack&); public: Stack(int); // conversion constructor void push(const Type&); // push on top of stack Type pop(); // pop the top symbol bool isEmpty() const; // is stack empty? ~Stack(); // return heap memory } ; These two objects, even though they have different initial array lengths, are of the same class, and one object can be used instead of another. Stack<int> stack1(20); // same type as other Stack<int> objects Stack<int> stack2(50); This is more flexible and convenient than the implementation with the expression parameter. In general, the template class with type parameters and a constructor parameter can do everything that a template class with the additional expression parameter and no constructor parameter can do, plus the objects of different initial length are compatible. Avoid templates with expression parameters unless their advantages over templates with class parameters only are evident. Relations Between Instantiations of Template Classes Template instantiations can be used as actual type arguments to instantiate other template classes. For example, you can create a stack of dictionary entries with the following declaration. Stack<DictEntry<Point,char*> > stackOfEntries; // 100 entries Notice an extra space between the two greater than signs. If you do not insert this space, the compiler will misunderstand the code and will shower you with a deluge of irrelevant error messages. None of these messages indicates that you need an extra space. This is the second place where C++ is not space blind. Another place where space is important is defining the default parameter value for a function parameter of a pointer type where the name of the parameter is not used. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1083 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm In the declaration above, the Stack instantiation prompts the DictEntry instantiation. An optimized compiler might cache object code for reuse in future compilations. If the compiler does not do that, compilation and link time can grow significantly. Template instantiations for different actual types (and expression values) are separate and have no relation or access to each other. For example, DictEntry<int,int> and DictEntry<float,record> are two independent distinct classes. So are instantiations for Stack<int> and Stack<float>. Objects of these types cannot be used one instead of another. A class can declare all its template instantiations to have a common base non-template class: template <class T> class Stack : public BaseStack { ¡K. } ; All instantiations of class Stack will have access to BaseStack objects according to the rules of inheritance. These instantiations cannot have access to each others non-public components. Template Classes as Friends A non-template class (or function) can be declared as a friend of all instantiations of a template class, if the use of instantiated objects does not depend on their type: template <class T> class Stack { friend class StackUser; ¡K. } ; Here, class StackUser has access to non-public components of any instantiation of class Stack, no matter what type is used as an actual type. Conversely, a template class (or function) can be declared as a friend of a non-template class even when its type parameter is not bound to any actual value. class Node { template <class T> friend class Stack; int item; file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1084 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm Node *next; // Node *next' is also OK Node(const int val) : item(val) { next = NULL; } } ; Here the Node class can support the information field and the link to the next node in the linked list. It does not need any member functions with the exception of the constructor that initializes both data fields. Each instantiation of the Stack class is a friend of the non-template class Node and has access to its non-public members. This might be useful if factoring out common code decreases the size (and compile/link time) of the object code. Instead of heap memory allocated at instantiation (or at array overflow), the Stack class can allocate a Node object every time data is pushed on the stack and deallocate the top Node object when the data is popped from the stack. However, this is not very useful. One type Node (e.g., with the integer information field) cannot accommodate different types of objects that the client code wants to push on the stack. This means that class Node has to be a template as well. This also means that class Node should be defined as a template class. Then different types of Stack objects can instantiate and access different types of the Node object with different types of the item field. template <class Type> // template class class Node { friend class Stack<T>; // any type of component Type item; Node<Type> *next; // Node *next' is also OK Node(const Type& val) : item(val) { next = NULL; } } ; The technical term for this use of templates is unbounded types. The parameter Type is independent of parameter T, and each parameter can accept any actual values independently of each other. Come to think of it, this is more than you need. Here, each instantiation of class Stack (e.g., for type float) has access to details of each instantiation of class Node (e.g., of class Point). This code does not implement a realistic model of the real world. We need to enforce a one-to-one mapping between related instantiations, so that a stack of integers becomes a friend of an integer node only, not of a node with other types of the item field. To file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1085 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm achieve that mapping, we can bind a friend (client) template class (in this case, Stack) to the same type(s) as the template class that provides services (in this case, Node). template <class Type> // template class class Node { friend class Stack<Type>; // same type of component Type item; Node<Type> *next; // Node *next' is also OK Node(const Type& val) : item(val) { next = NULL; } } ; Here, for each instantiation of Node to a specific type (e.g., of class Point,) the Stack instantiation to the same type (class Point) is made a friend to this instantiation of class Node. Class Stack now has a data member of class Node pointer that is initialized to zero in the Stack constructor. When the next node is pushed on the stack, this pointer points to the new node (and the new node points to the node that used to be the first node in the list). The member function isEmpty() checks whether this pointer is NULL or points to a node. This means that function pop() has to set this pointer to NULL when the last node is removed from the stack. template <class T> class Stack { Node<T> *top; // Node *top; is illegal here public: Stack() // default: no initial length { top = NULL; } void push(const T&); T pop(); int isEmpty() const { return top == NULL; } // does top point to a node? ~Stack(); } ; As for any template, the use of Node outside of Node definition must be qualified with the parameter list. This is why the Stack data member top cannot be of type Node*¡Xit should be of type Node<T>*, where T is the Stack type parameter. As a result of this qualification in the Stack definition, instantiation of a Stack class object results in the automatic instantiation of a Node class data field of the same type. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1086 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm The Stack method push() allocates a new Node object on the heap. The call to the Node constructor initializes the item field of the Node object to the value of the push() parameter. The next field of the new Node is set to point to the node that the Stack field top is currently pointing to, and the top field is reset to point to the new Node object. template <class T> void Stack<T>::push (const T& val) { Node<T> *p = new Node<T>(val); // type Node<T>, not Node if (p == NULL) { cout << "Out of memory\n"; exit(1); } p->next = top; // point it to first node top = p; } // point to new node There is no need to test for array overflow in push() because there is no array in this implementation. There is still the need to test whether the allocation of the Node object is successful. Notice the type of the pointer¡Xit is not Node*; it is Node<T>*. Similarly, when the heap space is requested by the operator new, it is of type Node<T>, not of type Node, The type T will be provided at the time of Stack instantiation. The Stack method pop() sets the local pointer (of type Node<T>, not just Node) to point to the first node of the stack, copies the information field into a local variable (of type T), moves the top field to point to the second node, and deletes the top node because it is not needed anymore. template <class T> T Stack<T>::pop() // return value of type T { Node<T> *p = top; // Node of type T, not Node T val = top->item; // get the value of type T top = top->next; // point to the second node delete p; // return top node to heap return val; } When pop() deletes the last node of the list (and there is no second node for the field top to point to), the top pointer becomes NULL again. Why? Because when the first node was inserted in push(), the statement p->next=top set this field to NULL (because the Stack constructor set the field top to NULL.) Make sure you see that the member functions of the same class are tightly coupled to each other through the class data. The source code of the member functions has to be coordinated to make sure the functions cooperate correctly. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1087 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm The Stack destructor has to scan the linked list of remaining nodes and return them to the heap. The local pointer p of type Node<T> with the component of type T is used again. It is set to point to the first node of the list. Notice that the pointer to the first node of the list, the data member top, is of the same type as pointer p: Node<T>. In a while loop, the top pointer moves to the next node, the node pointed to by the pointer p is deleted, and pointer p moves to point to the next node as well. template <class T> Stack<T>::~Stack() { Node<T> *p = top; // type Node of type T while (top != NULL) // top is 0 when no nodes { top = top->next; // point to the next node delete p; // delete the previous node p = top; } } // traverse to the next node The advantage of this approach is that class Node is independent of class Stack. This means that class Node can be used by other friend classes, for example, Queue, and List. Since all Node members (including the constructor) are private in this design, non-friend clients cannot create or access Node objects. Another approach is to provide each client with its own private server class. If the Node definition is nested within the private section of the client, then only that client (and its friends) can access class Node. This again raises the issue of coordination (mapping) between template definitions. Nested Template Classes With nested design, the definition of the template class Node is nested within the definition of the container class that handles Node objects. Since the Node definition is entirely within the scope of the container class (e.g., Stack,) the name Node is not visible to other potential clients (e.g., Queue and List.) Hence, Node members can be made public, and there is no need to declare its single client (e.g., Stack) as a friend to class Node. This is the first attempt on the design with nested classes. Class Node is defined using the keyword struct and all its members are public. template <class T> class Stack { template <class Type> // Is it legal? Is it needed? file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1088 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm struct Node { Type item; Node<Type> *next; // type depends on instantiation Node(const Type& val) : item(val) { next = NULL; } } ; Node<T> *top; // Stack data member public: Stack() // default: no initial length { top = NULL; } void push(const T&); T pop(); int isEmpty() const { return top == NULL; } // does top point to a node? ~Stack(); } ; There are two problems with this definition. First, some compilers do not accept nested template definitions¡Xthey can only process global templates. Second, there is no need to use unbounded template types. In this design, mapping between classes Stack and Node is one-to-many: For any type argument for class Stack, class Node can use any other type. A mapping between Stack and Node should be one-to-one rather than one-to-many: Class Stack needs a Node object that is instantiated to the same actual type as class Stack itself. A good way to achieve that is to define class Stack as a template and then define class Node as a regular non-template class that uses the Stack type parameter for its data member and for its method parameter types. template <class T> class Stack { struct Node { // it depends on parameter type T item; // same type as in Stack Node *next; // Node<T> is incorrect here Node(const T& val) : item(val) // same type as in Stack { next = NULL; } } ; Node *top; // it is not a template now public: Stack() // default: no initial length { top = NULL; } void push(const T&); T pop(); int isEmpty() const { return top == NULL; } // does top point to a node? ~Stack(); file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1089 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm } ; Each instantiation of the Stack templates generates a Node class that uses the same type as the Stack actual type argument. There is no need to qualify the type Node within the Stack definition. The same is true of Stack member functions. When a local pointer is defined in a member function, it is defined as a pointer to type Node, not a pointer to the type Node<T>. For example, method push() is almost the same as in the previous version, but the pointer p is defined differently. I commented out the previous version of the pointer definition so that you can compare both versions. template <class T> void Stack<T>::push (const T& val) // { Node<T> *p = new Node<T>(val); // type Node<T>, not Node { Node *p = new Node(val); // type Node, not Node<T> if (p == NULL) { cout << "Out of memory\n"; exit(1); } p->next = top; // point it to first node top = p; } // point to new node The same is true of other methods of the container class. Listing 17.5 shows the implementation of the template class Stack with a nested class Node. The output of the program is shown in Figure 17-5. Figure 17.5. Output for program in Listing 17.5. Example 17.5. Example of a template class with a nested server class. #include <iostream> using namespace std; template <class T> class Stack { struct Node { // it depends on parameter type file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (1090 of 1187) [8/17/2002 2:58:11 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... take the type parameter from the template parameter list and move it into the actual type list If the brackets of the template parameter list become empty as a result, this is fine For example, the Array template class header: template class Array { // remove class T from brackets // append to class name becomes template class Array { // empty template parameter list // actual... complex, and more difficult to understand Not all compilers support specializations well When one of the data types requires special treatment (most often, it is a character array), consider writing a separate class with a separate name, for example, CharArray The advantage of writing a separate class is that there is no doubt which class is used to instantiate the object The disadvantage is that there... In all cases, you should make sure that when one class is instantiated for a specific type, the second class is instantiated to the same type Templates with Static Members If a template class declares static data, each template instantiation will have a separate set of these static members All objects that belong to this particular instantiation will share the same static member(s), but they will have... have no access to static members that belong to an instantiation for a different actual type parameter For example, class Stack can declare its top data member static This is an interesting design alternative¡Xwith the top data member static, the data fields item and next can be moved to the class Stack itself as a non-static data member What is left in class Node then? Nothing It becomes redundant Hence,... Unregistered Version - http://www.simpopdf.com template // empty template list class Array { // type of specialization char* *data; // heap array of data int size; // size of the array Array(const Array&); operator = (const Array&); public: Array(char* items[], int n) : size(n) // conversion { data = new char*[n]; // allocate heap memory if (data==0) { cout . special treatment (most often, it is a character array), consider writing a separate class with a separate name, for example, CharArray. The advantage of writing a separate class is that there. <iostream> using namespace std; template <class T> class Array { T *data; // heap array of data int size; // size of the array Array(const Array&); operator = (const Array&); public: . instantiated to the same actual type as class Stack itself. A good way to achieve that is to define class Stack as a template and then define class Node as a regular non-template class that