Harold Serrano

View Original

Understanding Class Templates in C++

Code duplication is bad for your code. Think of it as a malignant tumor waiting to grow into a cancer. C++ offers Class Templates to avoid code duplication.

Like function templates, class templates provide class behavior for different types. For example, you can create a container class which can manage elements of a certain type.

First, let me show you how declare a class template and its implementation. Then I will show you how to create a class template.

Declaring a Class Template

Before the class declaration, a statement declares an identifier as a type parameter. For example:

template <typename T>
class MyClass{
//...
};

Inside the class template, you can use the parameter T as any type to declare members and methods. For example:

template <typename T>
class MyClass{
    private:
        std::vector<T> myElements;
    public:
        MyClass();  //constructor
        void push(T const& element);
        T getElement();
};

If you have to declare your own copy constructor and copy assignment, it would look like this:

template <typename T>
class MyClass{
//...
    MyClass (MyClass<T> const& u);  //copy constructor
    MyClass<T>& operator=(MyClass<T> const& u); //copy assignment
};

Implementation of Member functions

Member functions are specified as function templates. And you have to specify the full type qualification of the class template. For example:

template <typename T>
void MyClass::push(T const& element){
//...
}

Implementing a Class Template Stack

Let's create a class template that will mimic the functionality of a stack algorithm. This class will contain a vector container whose type depends on the template parameter.

template <typename T>
class Stack{

private:
    std::vector<T> elements;  //elements
public:
    Stack(){};
    ~Stack(){};
    void push(T const& uElement); //push element
    void pop();     //remove last element
    T top() const; //return last element

};

The class provides a vector container named elements along with three member functions.

The listing below shows the implementation of the function members.

template <typename T>
void Stack<T>::push(T const& uElement){
    elements.push_back(uElement);  //append the element
}

template <typename T>
void Stack<T>::pop(){
    //Normally you would do a check if the vector is not empty. Avoiding for simplicity
    elements.pop_back();  //remove last element
}

template <typename T>
T Stack<T>::top() const{
    //Normally you would do a check if the vector is not empty. Avoiding for simplicity
    return elements.back();     //return copy of last element
}

Using the Class Template Stack

To use a class template object, specify the template arguments explicitly. For example:

int main(int argc, const char * argv[]) {

    //create a stack of int type
    Stack<int> myIntStack;

    //append an integer to the container
    myIntStack.push(2);

    //create a stack of float type
    Stack<float> myFloatStack;

    //append a float to the container
    myFloatStack.push(4.2);

    return 0;
}

A class template allows you to create several versions of a class which can deal with different types. The Stack class above not only can deal with ints and floats but also with strings.

As you can see, Class Templates are simple to set up. Use them to avoid code duplication.

The Inclusion Model

If you are like any other developer, you will want to separate your template class into a declaration file (.h) and an implementation file (.cpp). However, doing so will normally result in a compiler error.

To fix this error you need to #include your implementation file at the end of your header file as shown below:

#ifndef Stack_hpp
#define Stack_hpp

#include <stdio.h>
#include <vector>
#include <stdexcept>
#include <iostream>

template<typename T>

class Stack{

    private:
    std::vector<T> elements; //elements of container

    public:
    Stack(); //constructor

    void push(T const& uElement); //push element

    T top()const;   //return top element
};

#include "Stack.cpp"  //#Included the implementation file
#endif

And in your implementation file (.cpp) you will need to use Header guards. I know, you normally use header guards in (.h) files, but this time you will also need to use them in your implementation file. See the snippet below:

#ifndef Stack_cpp  //Header guards 
#define Stack_cpp

#include "Stack.hpp"

template <typename T>
Stack<T>::Stack(){} //constructor

template <typename T>
void Stack<T>::push(T const& uElement){

    elements.push_back(uElement);  //push element into vector

}

template <typename T>
T Stack<T>::top() const{

    if (elements.empty()) {
        std::cout<<"Come on dude, I have nothing more to give";
        throw std::out_of_range("Stack<>::top():empty stack");
    }

    return elements.back(); //return copy of last element
}

#endif

Now you will be able to use your template in your main.c file:

#include "Stack.hpp"

int main(int argc, const char * argv[]) {

    //create Stack class of type int
    Stack<int> stackOfInts;

    //push value 2
    stackOfInts.push(2);

    //get last value
    int intValue=stackOfInts.top();

    std::cout<<intValue<<std::endl;

    return 0;
}

You can grab the Inclusion-Model code example from my github page.

I hope this tutorial helped. If you want, sign up and get monthly development tips.