Navigation



14.3 — Template classes

In the previous two lessons, you learn how function templates and function template instances could be used to generalize functions to work with many different data types. While this is a great start down the road to generalized programming, it doesn’t solve all of our problems. Let’s take a look at an example of one such problem, and see what templates can do for us further.

Templates and container classes

In the lesson on container classes, you learned how to use composition to implement classes that contained multiple instances of other classes. As one example of such a container, we took a look at the IntArray class. Here is a simplified example of that class:

#ifndef INTARRAY_H
#define INTARRAY_H

#include <assert.h> // for assert()

class IntArray
{
private:
    int m_nLength;
    int *m_pnData;

public:
    IntArray()
    {
        m_nLength = 0;
        m_pnData = 0;
    }

    IntArray(int nLength)
    {
        m_pnData = new int[nLength];
        m_nLength = nLength;
    }

    ~IntArray()
    {
        delete[] m_pnData;
    }

    void Erase()
    {
        delete[] m_pnData;
        // We need to make sure we set m_pnData to 0 here, otherwise it will
        // be left pointing at deallocated memory!
        m_pnData = 0;
        m_nLength = 0;
    }

    int& operator[](int nIndex)
    {
        assert(nIndex >= 0 && nIndex < m_nLength);
        return m_pnData[nIndex];
    }

    int GetLength() { return m_nLength; }
};

#endif

While this class provides an easy way to create arrays of integers, what if we want to create an array of doubles? Using traditional programming methods, we’d have to create an entirely new class! Here’s an example of DoubleArray, an array class used to hold doubles.

#ifndef DOUBLEARRAY_H
#define DOUBLEARRAY_H

#include <assert.h> // for assert()

class DoubleArray
{
private:
    int m_nLength;
    double *m_pdData;

public:
    DoubleArray()
    {
        m_nLength = 0;
        m_pdData= 0;
    }

    DoubleArray(int nLength)
    {
        m_pdData= new double[nLength];
        m_nLength = nLength;
    }

    ~DoubleArray()
    {
        delete[] m_pdData;
    }

    void Erase()
    {
        delete[] m_pdData;
        // We need to make sure we set m_pnData to 0 here, otherwise it will
        // be left pointing at deallocated memory!
        m_pdData= 0;
        m_nLength = 0;
    }

    double& operator[](int nIndex)
    {
        assert(nIndex >= 0 && nIndex < m_nLength);
        return m_pdData[nIndex];
    }

    // The length of the array is always an integer
    // It does not depend on the data type of the array
    int GetLength() { return m_nLength; }
};

#endif

Although the code listings are lengthy, you’ll note the two classes are almost identical! In fact, the only substantive difference is the contained data type. As you likely have guessed, this is another area where templates can be put to good use to free us from having to create classes that are bound to one specific data type.

Creating template classes is works pretty much identically to creating template functions, so we’ll proceed by example. Here’s the IntArray classes, templatated version:

#ifndef ARRAY_H
#define ARRAY_H

#include <assert.h> // for assert()

template <typename T>
class Array
{
private:
    int m_nLength;
    T *m_ptData;

public:
    Array()
    {
        m_nLength = 0;
        m_ptData = 0;
    }

    Array(int nLength)
    {
        m_ptData= new T[nLength];
        m_nLength = nLength;
    }

    ~Array()
    {
        delete[] m_ptData;
    }

    void Erase()
    {
        delete[] m_ptData;
        // We need to make sure we set m_pnData to 0 here, otherwise it will
        // be left pointing at deallocated memory!
        m_ptData= 0;
        m_nLength = 0;
    }

    T& operator[](int nIndex)
    {
        assert(nIndex >= 0 && nIndex < m_nLength);
        return m_ptData[nIndex];
    }

    // The length of the array is always an integer
    // It does not depend on the data type of the array
    int GetLength(); // templated GetLength() function defined below
};

template <typename T>
int Array<T>::GetLength() { return m_nLength; }

#endif

As you can see, this version is almost identical to the IntArray version, except we’ve added the template declaration, and changed the contained data type from int to T.

Note that we’ve also defined the GetLength() function outside of the class declaration. This isn’t necessary, but new programmers typically stumble when trying to do this for the first time due to the syntax, so an example is instructive. Each templated member function declared outside the class declaration needs its own template declaration. Also, note that the name of the templated array class is Array<T>, not Array — Array would refer to a non-templated version of a class named Array.

Here’s a short example using the above templated array class:

int main()
{
	Array<int> anArray(12);
	Array<double> adArray(12);

	for (int nCount = 0; nCount < 12; nCount++)
	{
		anArray[nCount] = nCount;
		adArray[nCount] = nCount + 0.5;
	}

	for (int nCount = 11; nCount >= 0; nCount----;)
		std::cout << anArray[nCount] << "\t" << adArray[nCount] << std::endl;

	return 0;
}

This example prints the following:

11     11.5
10     10.5
9       9.5
8       8.5
7       7.5
6       6.5
5       5.5
4       4.5
3       3.5
2       2.5
1       1.5
0       0.5

Templated classes are instanced in the same way templated functions are — the compile stencils a copy upon demand with the template parameter replaced by the actual data type the user needs and then compiles the copy. If you don’t ever use a template class, the compile won’t even compile it.

Template classes are ideal for implementing container classes, because it is highly desirable to have containers work across a wide variety of data types, and templates allow you to do so without duplicating code. Although the syntax is ugly, and the error messages can be cryptic, template classes are truly one of C++’s best and most useful features.

A note for users using older compilers

Some older compilers (eg. Visual Studio 6) have a bug where the definition of template class functions must be put in the same file as the template class is defined in. Thus, if the template class were defined in X.h, the function definitions would have to also go in X.h (not X.cpp). This issue should be fixed in most/all modern compilers.

14.4 — Expression parameters and template specialization
Index
14.2 — Function template instances


Tip: Are you printing many of the LearnCpp articles? You can lower your printing cost by comparing ink cartridge prices before buying.

14 comments to 14.3 — Template classes

You must be logged in to post a comment.