11.1.2.  Class Templates

[ fromfile: templates.xml id: classtemplates ]

Like functions, classes can also use parameterized types. A class template specifies how to produce generic data structures of a particular type. All Qt container classes and, of course, all containers in the Standard Template Library (STL) are parameterized. The parameter is the answer to the question, "Container of what?" Figure 11.1 shows a UML diagram of two template classes.

Figure 11.1.  Template-Based Stack

Template-Based Stack

UML locates the template parameter in a small offset box in the upper-right corner of the class box. Example 11.3 contains definitions for these classes.

Example 11.3. src/containers/stack/stack.h

[ . . . . ]

#include <QDebug>
template<class T> class Node {
 public:
    Node(T invalue): m_Value(invalue), m_Next(0) {}
    ~Node() ;
    T getValue() const {return m_Value;}
    void setValue(T value) {m_Value = value;}
    Node<T>* getNext() const {return m_Next;}
    void setNext(Node<T>* next) {m_Next = next;}
 private:
    T m_Value;
    Node<T>* m_Next;
};

template<class T> Node<T>::~Node() {
    qDebug() << m_Value << " deleted " << endl;
    if(m_Next) {
        delete m_Next; 
    }
}

template<class T> class Stack {
 public:
    Stack(): m_Head(0), m_Count(0) {}
    ~Stack<T>() {delete m_Head;} ;
    void push(const T& t);
    T pop();
    T top() const;
    int count() const;
 private:
    Node<T>* m_Head;
    int m_Count;
};

<include src="src/containers/stack/stack.h" mode="cpp" href="src/containers/stack/stack.h" id="stackh" segid="classdef"/>


All template definitions (classes and functions) must appear in the header file. This is necessary for the compiler to generate code from a template declaration.

Notice in both Example 11.3 and Example 11.4, the required template declaration code: template<class T> precedes each class or function definition that has a template parameter in its name.

Example 11.4. src/containers/stack/stack.h

[ . . . . ]


template <class T> void Stack<T>::push(const T& value) {
    Node<T>* newNode = new Node<T>(value);
    newNode->setNext(m_Head);
    m_Head = newNode;
    ++m_Count;
}

template <class T>  T Stack<T>::pop() {
    Node<T>* popped = m_Head;
    if (m_Head != 0) {
        m_Head = m_Head->getNext();
        T retval = popped->getValue();
        popped->setNext(0);
        delete popped;
        --m_Count;
        return retval;        
    }
    return 0;
}

<include src="src/containers/stack/stack.h" mode="cpp" href="src/containers/stack/stack.h" id="stackhdef" segid="fundef"/>


The creation of objects is handled generically in the template function, push(). The destructor for the Node<T> class recursively deletes Node pointers until it reaches one with a zero m_Next pointer.[83] Controlling creation and destruction of Node<T> objects this way enables Stack<T> to completely manage its dynamic memory. Example 11.5 contains some client code to demonstrate these classes.

Example 11.5. src/containers/stack/main.cpp

#include <QDebug>
#include <QString>
#include "stack.h"

int main() {
    Stack<int> intstack1, intstack2;
    int val;
    for (val = 0; val < 4; ++val) {
        intstack1.push(val);
        intstack2.push(2 * val);
    }
    while (intstack1.count()) {
        val = intstack1.pop();
        qDebug() << val;
    }
    Stack<QString> stringstack;
    stringstack.push("First on");
    stringstack.push("second on");
    stringstack.push("first off");
    QString val2;
    while (stringstack.count()) {
        val2 = stringstack.pop();
        qDebug() << val2;
    }
    qDebug() << "Now intstack2 will self destruct.";
    return 0;
}

<include src="src/containers/stack/main.cpp" href="src/containers/stack/main.cpp" id="stackmaincpp" mode="cpp"/>


When you run the program, you should see the following output.

3 deleted
3
2 deleted
2
1 deleted
1
0 deleted
0
first off deleted
"first off"
second on deleted
"second on"
First on deleted
"First on"
Now intstack2 will self destruct.
6 deleted
4 deleted
2 deleted
0 deleted
[Note] Q_OBJECT and Template Classes

Because additional code needs to be generated by moc for each Q_OBJECT, and moc is not smart enough to know how to generate specializations of a template class, it is not permitted to make a class template that is also marked Q_OBJECT.



[83] This is a consequence of the fact that calling delete on a pointer automatically invokes the destructor associated with that pointer.