C + + learning notes: class template

Posted by dmcentire on Fri, 11 Feb 2022 13:53:38 +0100

In the previous chapter, we introduced function templates. In today's chapter, we will learn class templates.

Class template declaration

Template is the keyword to declare a class template. It means to declare a template. The template parameters can be one or multiple. They can be * * type parameters * * or * * non type parameters** A type parameter consists of the keyword class or typename and its subsequent identifiers. The non type parameter is composed of a common parameter, which represents a constant in the template definition (the template parameter will be studied separately later).

//T is a type parameter and size is a non type parameter
template<class T, int size>
class Stack {
 // ...
};

A simple class template is declared as follows:

//A template class that defines a single template parameter
template<typename T>
class Stack {
    public:
     viod push(const T&);
     void pop();
     T top() const;
    
    private:
     std::vector<T> elems;
};

The declaration of class template is very similar to that of function template. Inside class Stack, type T can be used to declare member variables or functions like any other type. But you need to * * "note:"**

  1. If a type variable with the same name as the template parameter is declared in the global domain, the global variable is hidden and the template parameter is used inside the class.

  2. The template parameter name cannot define member variables or types with the same name repeatedly in the class template.

  3. The same template parameter name can only appear once in the template parameter table. When there are multiple template parameters, the name cannot be the same.

  4. Template parameter names can be reused in different class templates or declarations.

//Global type declaration
typedef std::string type;

template<typename type,int size>
class Stack {
 type node;  //node is not a string type, but a template parameter type
 typedef int type;  //Error: member name cannot have the same name as template parameter type
};

template<typename type, typename type>  //Error: reusing parameter named type
class Person;

template<typename type> //The parameter name "type" can be reused between different templates
class Vehicle;

Member function of class template

The member function of the class template can be implemented directly when the class template is defined (inline function), or it can be defined outside the class template definition (in this case, the member function definition must be preceded by template and template parameters).

The class template member function itself is also a template. When the class template is instantiated, it is not instantiated automatically. It is instantiated only when it is called or addressed. Next, the Stack class above defines a function empty() implemented directly inside the class and Push() implemented outside the class, as follows:

template<typename T>
class Stack {
    public:
     viod push(const T&);
     bool empty() const {
            return elems.empty();
        }
    
    private:
     std::vector<T> elems;
};

//Implementation of member function
template<typename T>
void Stack<T>::Push(const T& elem) {
    elems.push_back(elem);
}

// ...

The type of this template class is stack < T >, so when implementing the member function, you must use stack < T >:: as the domain.

Class template static data member

The static data members of the template class are the same as those of ordinary classes and ordinary functions. We want to leave some information after function calls, and this information changes with the number of function calls, that is, after the execution of the function or class object, it does not completely eliminate, but leaves a trace, such as the number of function calls, the number of object declarations, etc. Take class as an example. These variables are static variables. They exist in all class objects. We can modify them in each object and serve as a bridge between objects.

Static members can be defined in the class template. All classes instantiated from the class template contain the same static members.

#include<iostream>
using namespace std;
 
template <class T>
class A
{
 T m;
 static T n;
    static int count;
 public:
        A() {}
  A(T a):m(a){
            cout<<"Call A(T a)"<<m<<endl;
            n+=m;
        }

  void disp() {cout<<"m="<<m<<", n="<<n<<endl;}
        void print() {cout<<"count="<<count<<endl;}
};
 
template <class T>
T A<T>::n = 0; //Initialization of static data members

template<> 
int A<int>::count = 10;

template<> 
int A<double>::count = 2;


int main()
{
 A<int> a(2), b(3);
 a.disp();
 b.disp();
 A<double> c(1.2),d(4.6);
 c.disp();
 d.disp();

    A<int> e;
    e.print();
    A<double> f;
    f.print();

 return 0;
}

It is necessary to declare static member variables outside the class.

For the static variable n, it is also A "template variable". Each instance of A has its own n variable, that is, A, A < double > and A have separate n, which are not shared.

Just like the static variable count instantiated above, A and A are two different classes. Although they all have static member variable count, it is obvious that object e of A and object f of A < double > will not share A count. It can also be clearly seen from the following operation results:

Call A(T a)2
Call A(T a)3
m=2, n=5
m=3, n=5
Call A(T a)1.2
Call A(T a)4.6
m=1.2, n=5.8
m=4.6, n=5.8
count=10
count=2

static member functions or variables of class templates are instantiated only when used.

Class template instantiation

The process of generating a class from a generic class template definition is called template instantiation.

Template instantiation refers to the process of generating template functions (template classes) from function templates (class templates). For function templates, a real function will be generated after the template is instantiated. After instantiation, the class template only completes the class definition, and the member functions of the template class will not be initialized until they are called.

Instantiation can be divided into implicit instantiation and display instantiation. Generally, template instantiation is automatically completed by the compiler when calling a function or creating an object without the guidance of the programmer. Therefore, it is called implicit instantiation, as follows:

Stack<int> intStack;
Stack<std::string> strStack;

Explicitly telling the compiler which type to instantiate through code is called explicit instantiation; We must place the explicitly instantiated code in the source file (. cpp) that contains the template definition, not just the header file that contains the template declaration. In this way, you can put the declaration and definition of the template in different files. Explicit instantiation is as follows:

template class Stack<int>;
template class Stack<std::string>;

When a class template is implicitly instantiated, each member declaration of the template is also instantiated, but the corresponding definition is not instantiated. However, there are exceptions:

  1. If the class template contains an anonymous Union, the members defined by the union are also instantiated;

  2. As a result of instantiating the class template, the definition of virtual function may be instantiated, but it may not, which depends on the specific implementation;

The purpose of C + + supporting explicit instantiation is to provide a solution for "modular programming". Although this solution is effective, it also has obvious defects: programmers must instantiate all types used in the definition file (implementation file) of the template. This means that every time you change the template usage file (the file calling the function template or the file creating the object through the class template), you should also change the template definition file accordingly to increase the instantiation of new types or delete the instantiation of useless types.

A template may be used in multiple files. It is very difficult to keep these files updated synchronously. For the developers of the library, they can't assume what types the users will use in advance, so they can't use explicit instantiation at all. They can only put the declaration and definition (Implementation) of the template into the header file; Almost all C + + standard libraries are implemented with templates, and the code of these templates is also located in the header file.

In general, if the template we develop is only used by ourselves, we can reluctantly use explicit instantiation; If you want others to use (such as libraries, components, etc.), you can only put the declaration and definition of the template in the header file.

Class template specialization

The essence of specialization is to instantiate a template instead of overloading it. The special case does not affect the parameter matching. Parameter matching is based on the principle of best matching.

Sometimes, the first mock exam is written to fit a single template, which can be adapted to the public and make each type have the same function. But for a specific type, if a specific template is to be implemented, a single template can not be achieved.

template<>
class Stack<std::string> {
private:
    std::queue<std::string> elems;

public:
    void push(std::string const&);
    void pop();
    std::string const& top() const;
    bool empty() const {
        return elems.empty();
    }
};

void Stack<std::string>::push(std::string const& elem) {
    elems.push_back(elem);
}

void Stack<std::string>::pop() {
    assert(!elems.empty());
    elems.pop_back();
}

std::string const& Stack<std::string>::top() const {
    assert(!elems.empty());
    return elems.back();
}

For exceptional cases, a "template < >" must be declared at the starting declaration, indicating that all template parameters of the original template will be provided with arguments.

As in the above code, the member function of the specialized version will be defined as a normal member function, in which the template parameter T is replaced with the specialized type.

For the specialization of std::string type, use the std::queue container to manage elements (just to explain that the implementation of the specialization version can be different from that of the template).

"Note": the template and its special edition should be declared in the same header file, and the declarations of all templates with the same name should be put in the front, followed by the special edition.

Local specialization of class template

If the template class parameter is not specified, it must provide only one parameter part of the template class. If the template class parameter is not specified, only one parameter part of the template class must be specified.

Next, we define a class with multiple template parameters and local specialization in several different ways:

//Multi parameter class template
template<typename T1, typename T2>
class Stack {
    // ...
};

//Local specialization T2 to int type
template<typename T>
class Stack<T, int> {
    ...
};

//Local specialization T1 and T2 must be of the same type T
template<typename T>
class Stack<T, T> {
    ...
};

//Local specialization accepts only pointer types
template<typename T1, typename T2>
class Stack<T1*, T2*> {
    ...
};

Now let's take a look at the classic STD:: remove that uses special cases in the standard library_ Const type:

// remove_const prototype definition
template< class T >
struct remove_const {
    typedef T type;
};

//Const is a special case. Only const types are accepted
template< class T >
struct remove_const<const T> {
    typedef T type;
};

Let's write an example to verify the above functions:

int main() {
    int a = 1;
    const int b = 2;
    remove_const<decltype(a)>::type aa = 3;
    remove_const<decltype(b)>::type bb = 4;
    std::cout << std::is_same<decltype(aa), int>::value << std::endl;
    std::cout << std::is_same<decltype(bb), int>::value << std::endl;
    return 0;
}

Output:

true
true

If there is no const local specialization above, then remove_ The type of const receiving b is const int, so the second output should be false, because decltype(bb) should be equal to const int instead of int.

Default Template

Class template parameters can have default arguments. The order of providing default arguments to parameters is from right to left.

//If the second parameter is not passed, it defaults to STD:: vector < T1 >. Of course, std::deque can also be passed
template<typename T1, typename T2 = std::vector<T1>>
class Stack {
    ...
};

//If the second parameter is not passed, the default value is size=8
template<typename T, int size = 8>
class Vector {
    ...
};

Instantiation is as follows:

//int stack. By default, vector is used to manage elements
Stack<int> intStack;
//double stack, which uses std::deque to manage elements
Stack<double,std::deque<double> > dblStack;

Summary

  1. "During the implementation of the class template, it is better not to split the class and function implementation into two files." Directly at h file will complete the function implementation and class.

  2. If it is split into two files, both files must be include d in the file calling the function, otherwise the corresponding implementation code cannot be found, and an entity version of executable function cannot be generated.

  3. In the implementation of a class template, one or more types can be unspecified.

  4. In order to use the class template, you can pass in a specific type as the template argument; The compiler will then instantiate the class template based on the type.

  5. For class templates, only those called member functions will be instantiated.

  6. You can customize class templates with a specific type.

  7. You can use a specific type of local specialization class template.

  8. You can define default values for the parameters of the class template, and these values can also refer to the previous template parameters.

Topics: C++ Back-end Cpp