Detailed explanation of C + + class template: concept and basic usage

Similar to function templates( Detailed explanation of C + + function template (1): concept and characteristics ), classes can also be parameterized by one or more types. For example, a container class is a typical example of this feature, which is often used to manage certain types of elements. As long as we use class templates, we can implement container classes without having to determine the type of elements in the container.

1, Implementation of class template

In this post, we use Stack as an example of a class template.

(1.1) declaration of class template

#include <vector>
#include <deque>
#include <stdexcept>

template<typename T>
class Stack
{
    std::vector<T> elems;   // The container where the element is stored.

public:
    void push(const T& value);  // Push elements into the stack.
    void pop();                 // Pop the top element out of the stack.
    T top() const;              // View a copy of the top element of the stack.
    bool empty() const {        // Check if the stack is empty.
        return elems.empty();
    }
};

template<typename T>
void Stack<T>::push(const T& value)
{
    elems.push_back(value);
}

template<typename T>
void Stack<T>::pop()
{
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elems.pop_back();
}

template<typename T>
T Stack<T>::top() const
{
    if (elems.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    return elems.back();
}

As shown above, the declaration of a class template is similar to that of a function template: before declaration, we declare the identifier of the parameter type

template<typename /*class*/ T>
class Stack
{
    //...  
};

Of course, you can also use the keyword class instead of typename. Inside a class template, type T can be used to declare member variables and member functions just like other types. In this case, the type of the class is stack < T > where t is the template parameter. Therefore, when we need to use the type of this class in the declaration, we must use stack < T >. For example, if you want to declare your own implementation of copy constructors and assignment operators, you should write as follows:

template<typename T>
class Stack
{
    //...
    Stack(const Stack<T>& other);               // The constructor name here needs to be the same as the class name (Stack)
    Stack<T>& operator=(const Stack<T>& other); // The copy constructor here needs to use the type of the class (stack < T >)
    //...
};

However, when you need to use a class name instead of a class type, you should use only Stack. For example, when you specify the name of a class, or when you need to write a constructor or destructor, you need to use Stack.

(1.2) implementation of class template

In order to define the member function of a class template, we must specify that the member function is a function template (using template < typename T >), and we also need to use the full type qualification operator stack < T >:: of the class template. Therefore, the complete definition of member function push is as follows:

template<typename T>
void Stack<T>::push(const T& value)
{
    elems.push_back(value);
}

The implementation of other member functions is similar; it is the same as the definition of ordinary class, and it can also be written inline in the class, for example:

template<typename T>
class Stack
{
    std::vector<T> elems;   // The container where the element is stored.
    
public:
    // ...
    bool empty() const {        // Check if the stack is empty.
        return elems.empty();
    }
};

2, Use of class templates

See the following main function code:

int main()
{
    try 
    {
        Stack<int> intStack;
        Stack<std::string> stringStack;

        // Using int stack
        intStack.push(7);
        std::cout << intStack.top() << std::endl;

        // Using the string stack
        stringStack.push("hello");
        std::cout << stringStack.top() << std::endl;

        stringStack.pop();
        stringStack.pop();
    }
    catch (std::exception &ex)
    {
        std::cerr << "Exception: " << ex.what() << std::endl;
        return EXIT_FAILURE;
    }
    return 0;
}

Note: only those member functions that are called will generate the instantiation code of these functions.

Therefore, for this class template, the default constructor, push and top methods are instantiated for int and std::string. However, the pop method only provides the instantiation of std::string. The benefits of doing so are:

  • It can save time and space.
  • You can also use this type to instantiate class templates for types that do not provide all operations in all member functions.

On the other hand, if a class contains static members, each type used to instantiate them will instantiate them.

Tags: C++

Posted on Mon, 10 Feb 2020 21:50:39 -0800 by crash58