C + + template class and smart pointer

Posted by amclean on Wed, 17 Nov 2021 17:09:18 +0100

catalogue

1, Experimental content

2, Experimental process

1. Template function

1.1 general template functions

1.2 specialized template function

  2. Class template Queue

Type 2.1 formwork

2.2 member template function

2.3 formwork specialization

3. Template smart pointer

3.1 smart pointer

3.2 constructor

3.3 destructor

  3.4 copy constructor

3.5 overloading of operators such as equal sign, - >, * etc  

3.6 complete code

Summary:

1, Experimental content

Template function (compare)
        1. General template function
        2. Specialized template function
2, Class template Queue
        1. Class template (Queue)
        2. Member template function
        3. Template specialization: template function specialization, template member function specialization, and template class specialization
Three, template class AutoPtr
        1. Constructor
        2. Destructor
        3. Copy constructor
        4. Overloading of operators such as equal sign, - >, * etc
        5.6 main function call AutoPtr

2, Experimental process

1. Template function

         Function templates are general function descriptions. They use generics to define functions, in which generics can be replaced by specific types. By passing a type as an argument to a template, you enable the compiler to generate a function of that type. Because templates allow programs to be written in a generic (rather than a specific type), they are sometimes called general-purpose programming.

1.1 general template functions

A simple number comparison code:

 template <class Type>  //Using template to define the Type variable means that the Type can be any data Type
 int compare(const Type& v1, const Type& v2){
     if(v1<v2) return -1;
     if(v1>v2) return 1;
     return 0;
 }

This code allows the parameters passed in to be of any data type. It does not need to consider whether the parameters passed in are integer or character type, and implement a function alone.

Test code:

int main()
{
    cout<<compare(10,20)<<endl;
    cout<<compare(3.14,2.56)<<endl;
}

Operation results:

1.2 specialized template function

Template specialization: when instantiating a template, special processing is performed on specific types of arguments, that is, a special instance version is instantiated. When the template is used with the formal parameters defined by specialization, the specialized version will be called. When we want to compare strings, we need to specialize the template function.

template <>//The declaration is a template specialization function 
int compare<const char *>(const char* const& v1, const char * const& v2) //Specialization
{
    return strcmp(v1,v2); //Call string comparison function
}//Template functions can only be written in header files, while the implementation of specialized functions can only be written in CPP

  Test code:

int main()
{
   cout<<compare("azurlane","arknight")<<endl;
}

Operation results:

  2. Class template Queue

Type 2.1 formwork

We need to write multiple functions with similar forms and functions, so we have function templates to reduce repetitive work; We also need to write multiple classes with similar forms and functions, so c + + introduces the concept of class template. The compiler can automatically generate multiple classes from class template, avoiding the repeated work of programmers.

Using template class allows users to define a pattern for the class, so that some data members, parameters, return values or local variables of some member functions in the class can take different types (including system predefined and user-defined).

The class template in C + + is written as follows:

template <Type parameter table>
class Class template name{
    Member functions and member variables
};

Define a class template Queue and a management class QueueItem

template<class Type>      //Type represents the general data type, that is, any return value can be taken
class Queue;              
template<class Type>      //The definition and use of class template is actually to instantiate the class template into a specific class
class QueueItem
{
    Type item;
    QueueItem * next;
    QueueItem(const Type& val):item(val),next(NULL){}   //Parameter list constructor method
    friend Queue<Type>;      //Define the queue as a friend class to facilitate access to private variables
    friend ostream& operator<<(ostream &os,const Queue<Type> &que);
public:
    QueueItem<Type>* operator++()
    {
        return next;     //Returns a pointer to the next element in the queue
    }
    Type & operator*()  //Fetch stored elements
    {
        return item;
    }
};
 
template<class Type>
class Queue
{
public:
    Queue();
    Queue(const Queue &q);
    ~Queue();
    bool isEmpty();                        //Determine whether the queue is empty
    void push(const Type& val);          //Add elements to the queue
    void pop();                          //Delete element in queue
    void destroy();                      //Empty queue
    Type& front();                       //The first element in the output queue
    void copy_item(const Queue &orig);   //Copy all queues
    template<class It>                   //Member template function
    Queue(It beg,It end);                //New parameterized constructor
    template<class It>
    void assign(It beg,It end);          //Adds the specified data to the queue
    template<class It>
    void copy_items(It beg,It end);      //Copy partial queue
    //Functions that access headers and tails
    const QueueItem<Type>* Head() const{return head;}
    const QueueItem<Type>* End() const{return(tail==NULL)? NULL:tail;}
 
private:
    QueueItem<Type> * head;    //Queue header pointer
    QueueItem<Type> * tail;    //The end of queue pointer. Using the QueueItem class composed of template classes requires template declaration
    friend ostream& operator<<(ostream &os,const Queue<Type> &que)
    {
        os<<"< ";
        for(QueueItem<Type> * p = que.head;p!=NULL;p=p->next)
        {
 
            os<<p->item<<"  ";
        }
        os<<">";
        return os;
    }
};

2.2 member template function

//Remove data from queue header
template <class Type> void Queue<Type>::pop(){
    QueueItem<Type> * p =head;
    head = head->next;
    delete p; //Free up space
}

template <class Type> void Queue<Type>::destroy()
{ //Delete entire queue data
    while(!empty()){
        pop();
    }
}


//Data is inserted at the end of the queue
template <class Type> void Queue<Type>::push(const Type& val){
    QueueItem<Type> * pt = new QueueItem<Type>(val);
    if(empty()){
        head = tail = pt; //The head and tail point to the same data
    }
    else{
        tail->next = pt;
        tail=pt; //The tail pointer needs to always point to the last element
    }
}


template<class Type>
void Queue<Type>::copy_items(const Queue &orig){
    for(QueueItem<Type> * pt=orig.head;pt;pt=pt->next){
        push(pt->item);
    }
} //Insert all elements of queue orig into other queues, and the original queue elements remain

template <class Type>
Queue<Type>& Queue<Type>::operator=(const Queue& q){
    destroy();
    copy_items(q);
} 
template <class Type>
template<class It> void Queue<Type>::assign(It beg, It end)
{
    destroy();
    copy_items(beg, end);//Copies the queue area of the specified range
}

template <class Type>
template<class It> void Queue<Type>::copy_items(It beg, It end)
{
    while(beg!=end){
        push(beg);
        ++beg;
    } //Copy the queue elements of the specified range and insert them into the original queue
}

The member template function should be written in the. h file.

Test code:

//Class template definition object: class template name < real data type > object name;
    Queue<int> xwl;
    int c=12;
    xwl.push(5);//Add 5 to the list
    xwl.push(c);//Add c to the list
    cout<<xwl<<endl;
    xwl.pop();  //Delete first number
    xwl.push(4);//Add 4
    int dy=xwl.front();//Output first number
    cout<<dy<<endl;
    cout<<xwl<<endl;

    short a[5] = {1,4,5,7,9};
    Queue<int> xwl1(a,a+5);
    cout<<xwl1<<endl;
    while(!xwl1.isEmpty())
    {
        cout<<xwl1.front();
        xwl1.pop();
    }
    vector<int> vi(a,a+5);            
    xwl1.assign(vi.begin()+1,vi.end()-1);       //Add 4,5,7 to the list
    cout<<endl<<xwl1<<endl;

  Experimental results:

2.3 formwork specialization

2.3.1 template function specialization

When the template we write cannot adapt to all data types, we need to specialize some functions. In order to output the desired string results, we need to specialize the string data

//Specialized push function for char * data
template <>
void Queue<const char *>::push(const char * const & val){
    char* new_item = new char[strlen(val)+1]; //Create a character array based on the length of the string
    strncpy(new_item,val,strlen(val)+1); //Copy Character content
    QueueItem<const char* >*pt = new QueueItem<const char *>(new_item); //Declare template specialization char * class
    if(empty()){
        head = tail = pt;
    }
    else{
        tail->next = pt;
        tail = pt;
    }
}

template <>
void Queue<const char*>::pop(){
    QueueItem<const char *> *p = head; //Specialization template class QueueItem
    delete head->item;//char * data needs to be managed by itself, so it can be released by itself
    head = head->next;
    delete p; //Freeing pointer space

}

2.3.2 template specialization

Specialize the class template Queue for const char * data types

template <> class Queue<const char *>
{

private:
    void copy_items(const Queue &orig); //Copy start element
    QueueItem<const char *>* head;//The queue needs two pointers at the beginning and end
    QueueItem<const char *>* tail;//Using a QueueItem class consisting of a template class requires a template declaration
    void destroy(); //Free queue space
    template<class It> void copy_items(It beg, It end); //Specifies the range of copy queue elements
public:
    Queue():head(0),tail(0){}; //Parameter list constructor, initializing head pointer and tail pointer
    Queue(const Queue& q):head(0),tail(0){
        copy_items(q); //copy constructor 
    }
    template<class It>
    Queue(It beg, It end):head(0),tail(0){copy_items(beg,end);} //Specifies the scope copy constructor
    template<class It> void assign(It beg, It end);
    Queue& operator=(const Queue&);
    ~Queue(){destroy();} //Destructor
    const char *& front(){return head->item;} //Returns the top of the queue
    void push(const char *&val){ //Specialize const char * template function
        char* new_item = new char[strlen(val)+1]; //Create a character array based on the length of the string
            strncpy(new_item,val,strlen(val)+1); //Copy Character content
            QueueItem<const char* >*pt = new QueueItem<const char *>(new_item); //Declare template specialization char * class
            if(empty()){
                head = tail = pt;
            }
            else{
                tail->next = pt;
                tail = pt;
            }
    }; //Put elements in queue
    void pop(){
        QueueItem<const char *> *p = head; //Specialization template class QueueItem
            delete head->item;//char * data needs to be managed by itself, so it can be released by itself
            head = head->next;
            delete p; //Freeing pointer space
    }; //Remove queue header element
    bool empty() const{return head==0;} //Determine whether the queue element is empty
    friend ostream& operator<<(ostream& os, const Queue<const char *> &q)
    {
        os<<"< ";
        QueueItem<const char *> * p;
        for(p=q.head;p;p=p->next)
        {
            os<<p->item<<" ";
        }
        os<<">";
        return os;
    }
    //Functions that access headers and tails
    const QueueItem<const char *>* Head() const{return head;}
    const QueueItem<const char *>* End() const {return(tail==NULL)?NULL:tail;}


};

Test code:

Queue<const char*> xwl;
xwl.push("hi");
xwl.push("I'm");
xwl.push("wenlong xie");
cout<<endl<<xwl<<endl;
xwl.pop();
string str =xwl.front();
cout<<endl<<str<<endl;
cout<<endl<<xwl<<endl;

Operation results:

3. Template smart pointer

3.1 smart pointer

Smart pointers are class objects that behave like pointers, and all smart pointers will be overloaded  ->  and  *  Operator. Smart pointer is a class that stores pointers to dynamically allocated (heap) objects. It is used for lifetime control, which can ensure the automatic and correct destruction of dynamically allocated objects and prevent memory leakage. When initializing an object, an ordinary pointer cannot be directly assigned to a smart pointer, because one is a pointer and the other is a class. You can pass in a normal pointer through the constructor.

3.2 constructor

Constructors use templates like normal member functions

//Constructor to build an instance that uses a smart pointer
template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
    ptr = pData;
    user = new int(1);  //New outputs an int object and initializes it to 1; user = new int[1];new creates an array
}

3.3 destructor

  Generally speaking, we do not write a pile of code in the destructor, but call other functions through the destructor. Here, we release the data in the smart pointer at the same time

template<class T>
AutoPtr<T>::~AutoPtr()
{
    decrUser();
}
//When the number of users decreases, you should judge whether to release variable memory (= 0)
template<class T>
void AutoPtr<T>::decrUser()
{
    (*user)--;
    if((*user)==0)
    {
        delete ptr;
        ptr = 0;
        delete user;
        user = 0;
    }
}

  3.4 copy constructor

//The smart pointer assigns the initial value to the variable pointed to by another smart pointer
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& handle)
{
    ptr = handle.ptr;
    user = handle.user;
    (*user)++;  //Number of users of variable + 1
}

3.5 overloading of operators such as equal sign, - >, * etc  

//In class
T* operator->()         //Overload assignment operator "- >", return ptr pointer
{
    return ptr;
}
const T* operator->() const
{
    return ptr;
}
T operator*() const        //Overload the assignment operator "*", and take out the contents of the address pointed to by the pointer
{
    return *ptr;
}


//Class external
template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& handle)   //Overload assignment operator
{
    //If you reference yourself, the number of users remains the same
    if(this==&handle)
        return *this;
    //Before pointing to others, the number of variable users pointed to by itself is reduced by 1
    decrUser();
    ptr = handle.ptr;
    user = handle.user;
    (*user)++;
    return *this;
}

3.6 complete code

template<class T>
class AutoPtr
{
public:
    AutoPtr():ptr(0),user(0){};
    AutoPtr(T* pData);      //Constructor
    AutoPtr(const AutoPtr<T>& handle);
    ~AutoPtr();             //Destructor
    AutoPtr<T>& operator=(const AutoPtr<T>& handle);  //Overload assignment operator '='
    void decrUser();        //Reduce the number of users

    T* operator->()         //Overload assignment operator "- >", return ptr pointer
    {
        return ptr;
    }
    const T* operator->() const
    {
        return ptr;
    }
    T operator*() const        //Overload the assignment operator "*", and take out the contents of the address pointed to by the pointer
    {
        return *ptr;
    }
    T* get() const {return ptr;}   //Return saved pointer
    T getUser() const {return *user;}; //Returns the number of saved users
private:
    T* ptr = 0;            //Pointer to data
    int* user=0;          //Pointer to the number of users. Use * because it needs to be modified uniformly
};
//Constructor to build an instance that uses a smart pointer
template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
    ptr = pData;
    user = new int(1);  //New outputs an int object and initializes it to 1; user = new int[1];new creates an array
}
//The smart pointer assigns the initial value to the variable pointed to by another smart pointer
template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& handle)
{
    ptr = handle.ptr;
    user = handle.user;
    (*user)++;  //Number of users of variable + 1
}
template<class T>
AutoPtr<T>::~AutoPtr()
{
    decrUser();
}
template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& handle)   //Overload assignment operator
{
    //If you reference yourself, the number of users remains the same
    if(this==&handle)
        return *this;
    //Before pointing to others, the number of variable users pointed to by itself is reduced by 1
    decrUser();
    ptr = handle.ptr;
    user = handle.user;
    (*user)++;
    return *this;
}

//When the number of users decreases, you should judge whether to release variable memory (= 0)
template<class T>
void AutoPtr<T>::decrUser()
{
    (*user)--;
    if((*user)==0)
    {
        delete ptr;
        ptr = 0;
        delete user;
        user = 0;
    }
}

Test code:

int s1 = 15;
    int s2 = s1;
    AutoPtr<int> x1(new int(5));
    AutoPtr<int> x2(new int(s1));
    AutoPtr<int> x3(new int(s2));
    if(x1.get()==0)
    {
        cout<<"x1 is NULL"<<endl;
    }
    cout<<"before:x1 value is: "<<x1.get()<<endl;
    if(x2.get()==0)
    {
        cout<<"x2 is NULL"<<endl;
    }
    cout<<"before:x2 value is: "<<x2.get()<<endl;
    if(x3.get()==0)
    {
        cout<<"x3 is NULL"<<endl;
    }
    cout<<"before:x3 value is: "<<x3.get()<<endl;
    x1=x2;
    cout<<"after:x1 value is: "<<x1.get()<<endl;
    cout<<"after:x2 value is: "<<x2.get()<<endl;
    x3=x1;
    cout<<"after:x1 value is: "<<x1.get()<<endl;
    cout<<"after:x3 value is: "<<x3.get()<<endl;
    cout<<"x3->data:"<<*x3<<endl;
    cout<<"x3-user:"<<x3.getUser()<<endl;

Operation results:

  X1 starts to point to 5, the address is 0x711770,x2 points to 15, the address is 0x711ae0, x3 points to s2, make s2 = s1=15, the address is 0x711b20, then execute x1=x2, let X1 point to x2, the address becomes 0x711ae0, then execute x3=x1, x3 address becomes 0x711ae0, and X1 points to the same data s1=15. At this time, x1,x2,x3 all point to s1, the number of users is 3, and the life cycle ends, Execute the destructor to release three pointers.

Summary:

1. The implementation of class templates and their member functions is written in the. h file, and the specialized templates are written in the. cpp file.

2. The smart pointer automatically releases the memory space at the end of the function without manually releasing the memory space, which plays a good role in memory leakage.

Topics: C++