06C + + review Template

Posted by Drakla on Thu, 10 Mar 2022 12:12:46 +0100

C + + template

Templates are the foundation of generic programming, which is to write code in a way independent of any specific type.

1. Function template

    float swap(float &a, float &b){float temp = a;a = b; b=temp;}
    char swap(char &a, char &b){char temp = a;a = b; b=temp;}

Among the above three statements, int float char has three data types, but their specific functions are the same, so we just want to pass in the function as a parameter, and then let the computer help us write these three statements.

Function template keyword: template; typename;class

Function template can be used to create a general function to support a variety of different formal parameters and avoid repeated design of function body of overloaded function. Its biggest feature is to take the data type used by the function as the parameter.

The declaration form of function template is:

    <Return type><Function name>(Parameter table)
    {
	  Function body
    }

[example]

template<typename T> void swap(T& t1, T& t2);

#include "method.cpp"
void swap(T& t1, T& t2) {
    T tmpT;
    tmpT = t1;
    t1 = t2;
    t2 = tmpT;
}
#include <stdio.h>
#include "method.h"
int main() {
    //Template method 
    int num1 = 1, num2 = 2;
    swap<int>(num1, num2);
    printf("num1:%d, num2:%d\n", num1, num2);  
    return 0;
}

If you use the swap function here, you must include the definition of swap, otherwise the compilation will make an error. This is different from the use of general functions. So it must be in method Add #include "method.cpp" to the H file.

2. Class template

Just as we define function templates, we can also define class templates. The general form of generic class declaration is as follows:

{
	......
}

Here, type is the placeholder type name, which can be specified when the class is instantiated. You can define multiple generic data types using a comma separated list.

Consider that we write a simple stack class. This stack can support int type, long type, string type, etc. without using the class template, we need to write more than three stack classes, in which the code is basically the same. Through the class template, we can define a simple stack template, and then instantiate it into int stack, long stack and string stack as needed.
Class template definition

//statck.h
template <class T> 
class Stack {
public:
    Stack();//Constructor
    ~Stack();//Destructor
    void push(T t);
    T pop();
    bool isEmpty();
private:
    T *m_pT;        
    int m_maxSize;
    int m_size;
};
//Constructor
template <class  T>  
Stack<T>::Stack(){
   m_maxSize = 100;      
   m_size = 0;
   m_pT = new T[m_maxSize];
}
//Destructor
template <class T>  
Stack<T>::~Stack() {
   delete [] m_pT ;
}
        
template <class T> 
void Stack<T>::push(T t) {
    m_size++;
    m_pT[m_size - 1] = t;
    
}
template <class T> 
T Stack<T>::pop() {
    T t = m_pT[m_size - 1];
    m_size--;
    return t;
}
template <class T> 
bool Stack<T>::isEmpty() {
    return m_size == 0;
}
#include <stdio.h>
#include "stack.h"
int main() {
    Stack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    intStack.push(3);
    
    while (!intStack.isEmpty()) {
        printf("num:%d\n", intStack.pop());
    }
    return 0;
}

3. Template parameters

Templates can have type parameters, general type parameters int, or default template parameters, for example:

class Stack
{
	...
}

In the example of class template, there is a limitation that it can only support 100 elements at most. We can use the template parameter to configure the maximum number of elements of this stack. If not configured, set the default maximum value to 100. The code is as follows:

template <class T,int maxsize = 100> 
class Stack {
public:
    Stack();
    ~Stack();
    void push(T t);
    T pop();
    bool isEmpty();
private:
    T *m_pT;        
    int m_maxSize;
    int m_size;
};
template <class T,int maxsize> 
Stack<T, maxsize>::Stack(){
   m_maxSize = maxsize;      
   m_size = 0;
   m_pT = new T[m_maxSize];
}
template <class T,int maxsize>  
Stack<T, maxsize>::~Stack() {
   delete [] m_pT ;
}
        
template <class T,int maxsize> 
void Stack<T, maxsize>::push(T t) {
    m_size++;
    m_pT[m_size - 1] = t;
    
}
template <class T,int maxsize> 
T Stack<T, maxsize>::pop() {
    T t = m_pT[m_size - 1];
    m_size--;
    return t;
}
template <class T,int maxsize> 
bool Stack<T, maxsize>::isEmpty() {
    return m_size == 0;
}
#include <stdio.h>
#include "stack.h"
int main() {
    int maxsize = 1024;
    Stack<int,1024> intStack;
    for (int i = 0; i < maxsize; i++) {
        intStack.push(i);
    }
    while (!intStack.isEmpty()) {
        printf("num:%d\n", intStack.pop());
    }
    return 0;
}

4. Template specialization

When we want to define different implementations of templates, we can use template specialization. For example, if the stack class template we defined is a stack of char * type, we hope to copy all the data of char into the stack class, because only the char pointer is saved, the memory pointed to by the char pointer may become invalid, and the memory pointed to by the char pointer of the stack element popped up by stack may become invalid. In addition, the swap function template defined by us. In the case of container types such as vector or list, if the object saved in the container is large, it will occupy a lot of memory and degrade the performance. Because a temporary large object saving a needs to be solved by specialization of the template.

(1) Function template specialization

Suppose that our swap function needs to deal with a situation. We have two vectors with many elements. When using the original swap function to execute tmpT = t1, we need to copy all elements of t1, occupy a lot of memory and cause performance degradation, so our system passes through vector The swap function solves this problem. The code is as follows:

#include "method.cpp"

template<class T> void swap(T& t1, T& t2);
using namespace std;
template<class T> 
void swap(T& t1, T& t2) {
    T tmpT;
    tmpT = t1;
    t1 = t2;
    t2 = tmpT;
}
//Template specialization
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
    t1.swap(t2);
}

The prefix template < > indicates that this is a specialization. Template parameters are not used in the description. Examples are as follows:

#include <stdio.h>
#include <vector>
#include <string>
#include "method.h"
int main() {
    using namespace std;
    //Template method 
    string str1 = "1", str2 = "2";
    swap(str1, str2);
    printf("str1:%s, str2:%s\n", str1.c_str(), str2.c_str());  
    
    vector<int> v1, v2;
    v1.push_back(1);
    v2.push_back(2);
    swap(v1, v2);
    for (int i = 0; i < v1.size(); i++) {
        printf("v1[%d]:%d\n", i, v1[i]);
    }
    for (int i = 0; i < v2.size(); i++) {
        printf("v2[%d]:%d\n", i, v2[i]);
    }
    return 0;
}

The swap code of vector is still relatively limited. If you want to use template specialization to solve the swap of all vectors, what should you do? Just put the following code

    t1.swap(t2);
}

Change to

    t1.swap(t2);
}

It's OK. Other codes remain the same.
(2) Class template specialization

template <class T>
class compare
{
public:
  bool equal(T t1, T t2)
  {
       return t1 == t2;
  }
};
#include "compare.h"
int main()
{
  using namespace std;
  char str1[] = "Hello";
  char str2[] = "Hello";
  compare<int> c1;
  compare<char *> c2;   
  cout << c1.equal(1, 1) << endl;        //Compare two arguments of type int
  cout << c2.equal(str1, str2) << endl;   //Compare two parameters of type char *
  return 0;
}

When comparing two integers, the equal method of compare is correct, but when the template parameter of compare is char *, the template will not work, so it is modified as follows:

#include <string.h>
template <class T>
class compare
{
public:
  bool equal(T t1, T t2)
  {
       return t1 == t2;
  }
};
   
template<>class compare<char *>  
{
public:
    bool equal(char* t1, char* t2)
    {
        return strcmp(t1, t2) == 0;
    }
};

main.cpp file unchanged, this code can work normally.

5. Template type conversion

Remember our customized Stack template? In our program, suppose we define Shape and Circle classes. The code is as follows:

class Shape {

};
class Circle : public Shape {
};

Then we want to use it this way:

#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
    Stack<Circle*> pcircleStack;
    Stack<Shape*> pshapeStack;
    pcircleStack.push(new Circle);
    pshapeStack = pcircleStack;
    return 0;
}

It cannot be compiled here because Stack < Shape > is not the parent class of Stack < circle >, but we want the code to work like this. Then we need to define the conversion operator. The Stack code is as follows:

template <class T> 
class Stack {
public:
    Stack();
    ~Stack();
    void push(T t);
    T pop();
    bool isEmpty();
    template<class T2>  operator Stack<T2>();
private:
    T *m_pT;        
    int m_maxSize;
    int m_size;
};

#include "stack.cpp"
Stack<T>::Stack(){
   m_maxSize = 100;      
   m_size = 0;
   m_pT = new T[m_maxSize];
}
template <class T>  
Stack<T>::~Stack() {
   delete [] m_pT ;
}
        
template <class T> 
void Stack<T>::push(T t) {
    m_size++;
    m_pT[m_size - 1] = t;
    
}
template <class T> 
T Stack<T>::pop() {
    T t = m_pT[m_size - 1];
    m_size--;
    return t;
}
template <class T> 
bool Stack<T>::isEmpty() {
    return m_size == 0;
}

template <class T> template <class T2>  
Stack<T>::operator Stack<T2>() {
    Stack<T2> StackT2;
    for (int i = 0; i < m_size; i++) {
        StackT2.push((T2)m_pT[m_size - 1]);
    }
    return StackT2;
}
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
    Stack<Circle*> pcircleStack;
    Stack<Shape*> pshapeStack;
    pcircleStack.push(new Circle);
    pshapeStack = pcircleStack;
    return 0;
}

In this way, Stack or Stack < circle > can be automatically converted to Stack or Stack < Shape >. If the conversion type is Stack to Stack, the compiler will report an error.

6. Other

A class has no template parameters, but the member function has template parameters, which is feasible. The code is as follows:

public:
    template <class T> bool equal(T t1, T t2) {
        return t1 == t2;
    }
};

int main() {
    Util util;
    int a = 1, b = 2;
    util.equal<int>(1, 2);
    return 0;
}

You can even declare Util's equal as static. The code is as follows:

public:
     template <class T> static bool equal(T t1, T t2) {
        return t1 == t2;
    }
};

int main() {
    int a = 1, b = 2;
    Util::equal<int>(1, 2);
    return 0;
}

Topics: C++