Skip to content

Template Classes

Template classes provide a way to create data structures and objects that are independent of the data types they hold. Much like template functions, template classes allow you to write the logic for a single, generic class without tying it to a specific type.

Like in CSCE146, this approach is used to create container classes—such as arrays, vectors, lists, stacks, queues, and strings—that can work with any type of element.

Defining a template class in C++ is conceptually similar to defining a template function. You begin with a template directive, then declare one or more type parameters inside angle brackets:

myclass.h
template <class T>
class MyClass {
public:
void MyFunc() { cout << "hello" << endl; }
void AnotherFunc() {
// do something
}
private:
// data members
};

To create an object of a template class, specify the type you want to use in angle brackets when declaring your object:

driver.cc
MyClass<int> intObject;
MyClass<std::string> strObject;
intObject.MyFunc();

You may remember creating a Generic Dynamic Array in CSCE146. Lets create one for CSCE240 using template classes.

  1. Create Header

    Create you header file, write your header guards, and import everything you need. This dynamic array will keep track of its current size, and capacity and the class will be defined in a namespace called csce_240:

    darray.h
    // Copyright 2024 CSCE240
    #ifndef D_ARRAY_H
    #define D_ARRAY_H
    #include <iostream>
    using std::cout;
    using std::endl;
    namespace csce_240 {
    template <class T>
    class DynamicArray {
    public:
    // TODO
    private:
    int size_;
    int capacity_;
    T* array_;
    };
    } // namespace csce_240
    #endif
  2. Constructors and Destructor

    Lets create a constructor for the class. We’ll make the initial capacity 10 and double it whenever we need to resize.

    We’ll also create a copy constructor and fill the array with the new elements. We also need a destructor since we’re using a pointer as a data member:

    darray.h
    // constructor
    DynamicArray() : size_(0), capacity_(10), array_(new T[capacity_]) {}
    // copy constructor
    DynamicArray(const DynamicArray& el)
    : size_(el.size_), capacity_(el.capacity_), array_(new T[capacity_]) {
    for (int i = 0; i < size_; ++i) {
    array_[i] = el.array_[i];
    }
    }
    // destructor
    ~DynamicArray() {
    if (array_ != nullptr) delete[] array_;
    }
  3. Accessors and Operator

    Next we’ll define Getters for the size and capacity of the array:

    darray.h
    int GetSize() const { return size_; }
    int GetCapacity() const { return capacity_; }

    Since we have a pointer as a data member, we also need to overload the assignment operator:

    darray.h
    // operator overload
    DynamicArray& operator=(const DynamicArray& el) {
    size_ = el.size_;
    capacity_ = el.capacity_;
    delete[] array_;
    for (int i = 0; i < size_; ++i) {
    array_[i] = el.array_[i];
    }
    return *this;
    }
  4. Utility Functions

    The first utility function to define is a function to print all the elements that were added to the array. We can keep this function public since its especially useful for testing:

    darray.h
    void Print() const {
    for (int i = 0; i < size_; ++i) {
    cout << array_[i] << " ";
    }
    cout << endl;
    }

    We need to implement a way to add elements to the array, but we also have to consider how it will resize. How the array grows is something we can move to the private section of our class. It is a function that should only be called when necessary and an object should not have access to it:

    Public Add

    darray.h
    // public utilities
    void Add(T el) {
    if (size_ == capacity_) {
    Resize_();
    array_[size_] = el;
    capacity_ *= 2;
    ++size_;
    } else {
    array_[size_] = el;
    ++size_;
    }
    }

    Private Resize

    darray.h
    // private utilities
    void Resize_() {
    T* temp = new T[capacity_ * 2];
    // fill array
    for (int i = 0; i < size_; ++i) {
    temp[i] = array_[i];
    }
    delete[] array_;
    array_ = temp;
    }
  5. Test

    Create a driver file, include the header file, and use the new class we created. We’ll add a couple of elements to an array then print it. We’ll then test the copy constructor and the overloaded assignment operator:

    driver.cc
    // Copyright 2024 CSCE240
    #include <iostream>
    #include "darray.h"
    using csce_240::DynamicArray;
    using std::cout;
    using std::endl;
    int main() {
    DynamicArray<int> array;
    for (int i = 0; i < 15; ++i) {
    array.Add(i);
    }
    array.Print();
    cout << endl;
    DynamicArray<int> another(array);
    another.Print();
    cout << endl;
    DynamicArray<int> final = another;
    final.Print();
    return 0;
    }