Rebirth of C++ Cake Hands (Memory Management and Templates)

Memory management and templates

  • General Citation
  • 1. Memory management
    • 1.1 Definition of new and delete
    • 1.2 new and delete operation custom type
    • 1.3 operator new and operator delete functions
    • 1.4 How new and delete work
      • 1. **Built-in types:**
      • 2. **Custom Type:**
    • 1.5 The difference between new and delete
  • 2. Template
    • 2.1 Generic programming
    • 2.2 The concept of template
    • 2.3 The principle of the template
    • 2.4 Instantiation of Template Types
      • 1. **Implicit instantiation**: let the compiler deduce the actual type of the template parameter according to the actual parameter
      • 2. **Explicit instantiation**: specify the actual type of the template parameter in <> after the function name
    • 2.5 Matching of Template Parameters
    • 2.3 Class Templates
  • Summarize

General Citation

After learning classes and objects, our C++ learning ushered in a new chapter: memory management and templates. In this article, we will learn the two operators new and delete that replace malloc and realloc in C++, as well as their specific usage scenarios and underlying logic. We will also learn templates in C++ to make our object creation more convenient and flexible.

1. Memory management

In C language, if we want to apply for space, we will use malloc, realloc, calloc and other functions to apply, but in C++ we have learned custom types, when we want to apply for space for custom types, the above functions will be It seems stretched, so C++ defines new operators new and delte.

1.1 Definition of new and delete

The C language memory management method can continue to be used in C ++, but it is powerless in some places, and it is more troublesome to use, because
This C++ has proposed its own memory management method: dynamic memory management through new and delete operators.

#include <iostream>
using namespace std;
#include <malloc.h>
int main()
{<!-- -->
//C language memory development method

//Apply for space
//Apply for a space of type int
int* p1 = (int*)malloc(sizeof(int));

//Expand p1 to two spaces of type int
int* p2 = (int*)realloc(p1, sizeof(int) * 2);

//Apply for 10 spaces of type int and initialize them to 4
int* p3 = (int*)calloc(4, sizeof(int) * 10);

// release space
//free(p1) //p1 does not need to be free
free(p2);
free(p3);

//C + + memory development method

//Apply for space
//Apply for a space of type int
int* p4 = new int;

//Apply for a space of type int and initialize it to 1
int* p5 = new int(1);

//Apply for 10 spaces of type int and initialize them to 4
int* p6 = new int[10]{<!-- --> 4,4,4,4,4,4,4,4,4,4 };

// release space
delete p4;
//When releasing more than one space
delete[] p5;
delete[] p6;
\t
return 0;
}

Note: To apply for and release the space of a single element, use the new and delete operators, to apply for and release continuous space, use new[] and delete[], note: use them together.

1.2 new and delete operation custom type

When facing built-in types, new and delete can’t show their advantages so that we feel that it is not much different from malloc, but when facing custom types, new and delete can show their glory.

class A
{<!-- -->
public:
A(int date = 0)
:_date(date)
{<!-- -->
cout << "A(int date)" << endl;
}
~A()
{<!-- -->
cout << "~A()" << endl;
}
private:
int_date;
};

int main()
{<!-- -->
//C language
A* a1 = (A*)malloc(sizeof(A));
//C++
A* a2 = new A(1);

free(a1);
delete a2;
//The biggest difference between new and delete when facing a custom type is
//new will call the constructor, and delete will call the destructor.
return 0;
}

Note: When applying for a custom type of space, new will call the constructor, and delete will call the destructor, while malloc and free do not meeting.

1.3 operator new and operator delete functions

new and delete are operators for dynamic memory application and release, operator new and operator delete are
The global function provided by the system, new calls the operator new global function at the bottom to apply for space, and delete passes at the bottom
operator delete
global function to free up space.

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{<!-- -->
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
     if (_callnewh(size) == 0)
     {<!-- -->
         // report no memory
         // If the memory application fails, a bad_alloc type exception will be thrown here
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
return (p);
}
/*
operator delete: This function finally releases space through free
*/
void operator delete(void *pUserData)
{<!-- -->
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK); /* block other threads */
     __TRY
      /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK); /* release other threads */
     __END_TRY_FINALLY
     return;
}
/*
Realization of free
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

Through the implementation of the above two global functions, we know that operator new actually applies for space through malloc, if
malloc returns directly if the application space is successful, otherwise executes the countermeasures provided by the user for insufficient space, and continues to apply if the user provides the measures, otherwise throws an exception. operator delete finally releases space through free.

As for the behavior of throwing an exception, you can get a glimpse of it through the following code:

void Func()
{<!-- -->
int* p1 = new int[100000000];
//When the memory created with new is too large, we can use debugging to find that the compiler will not perform the following stream insertion
// Instead, jump directly to the stream insertion in the catch, which is somewhat similar to goto in C language.
cout << "hello world" << endl;
}

int main()
{<!-- -->
try
{<!-- -->
Func();
}
catch (const exception & e)
{<!-- -->
cout << e.what() << endl;
}
return 0;
}

1.4 How new and delete work

1. Built-in types:

If you are applying for a built-in type of space, new and malloc, delete and free are basically similar, the differences are:
new/delete applies and releases the space of a single element, new[] and delete[] apply for continuous space, and new will throw an exception when it fails to apply for space, and malloc will return NULL.

2.Custom Type:

The principle of new

  1. Call the operator new function to apply for space
  2. Execute the constructor on the requested space to complete the construction of the object

The principle of delete

  1. Execute the destructor on the space to complete the cleanup of resources in the object
  2. Call the operator delete function to release the object’s space

The principle of new T[N]

  1. Call the operator new[] function, and actually call the operator new function in operator new[] to complete the application of N object spaces
  2. Execute the constructor N times on the requested space

The principle of delete[]

  1. Execute N times of destructors on the released object space to complete the cleanup of resources in N objects
  2. Call operator delete[] to free up space, actually call operator delete in operator delete[] to free up space

1.5 The difference between new and delete

What malloc/free and new/delete have in common is that they all apply for space from the heap and need to be released manually by the user. The differences are:

  1. malloc and free are functions, new and delete are operators
  2. The space requested by malloc will not be initialized, but new can be initialized
  3. When malloc applies for space, you need to manually calculate the size of the space and pass it on. New just needs to follow it with the type of space. If there are multiple objects, specify the number of objects in []
  4. The return value of malloc is void*, which must be forced when used, and new does not need it, because new is followed by the type of space
  5. When malloc fails to apply for space, it returns NULL, so it must be judged as empty when using it. New does not need it, but new needs it.
    to catch the exception
  6. When applying for a custom type object, malloc/free will only open up space and will not call constructors and destructors, while new
    After applying for space, the constructor will be called to complete the initialization of the object, and delete will call the destructor to clean up the resources in the space before releasing the space

Two. Template

In the C language, we often use some functions that may only have different types of parameters. At that time, we are forced to give these functions different names.

2.1 Generic programming

In C++, we learned the concept of function overloading, so we can use the same function name but different parameter types to make it easier to use.

void Swap(int & amp; left, int & amp; right)
{<!-- -->
int tmp = left;
left = right;
right = tmp;
}
void Swap(double & amp; left, double & amp; right)
{<!-- -->
double tmp = left;
left = right;
right = tmp;
}
void Swap(char & amp; left, char & amp; right)
{<!-- -->
char tmp = left;
left = right;
right = tmp;
}
int main()
{<!-- -->
int a = 0;
int b = 1;
Swap(a, b);
double c = 1.1;
double d = 2.1;
Swap(c, d);
char e = 'e';
char f = 'f';
Swap(e, f);
cout << a << ' ' << b << endl;
cout << c << ' ' << d << endl;
cout << e << ' ' << f << endl;

return 0;
}

But we still need to write the function bodies of these functions one by one, which still reduces the readability of the code. Can we tell the compiler a model and let the compiler use the model to generate code according to different types? ? That is template.

Generic programming: Writing generic code that has nothing to do with types is a means of code reuse. Templates are the foundation of generic programming.

2.2 The concept of template

A function template represents a family of functions. The function template has nothing to do with the type. It is parameterized when used, and a specific type version of the function is generated according to the type of the actual parameter.

template <typename T>
void Swap(T & amp; left, T & amp; right)
{<!-- -->
T tmp = left;
left = right;
right = tmp;
}

Note: typename is used to define template parameter keywords, and class can also be used (remember: struct cannot be used instead of class)

2.3 The principle of the template

A function template is a blueprint. It is not a function itself, but a mold for the compiler to generate a specific type of function by using it. So in fact, the template is to hand over the repetitive things that we should have done to the compiler.

In the compiler compilation stage, for the use of template functions, the compiler needs to deduce and generate corresponding types of functions for calling according to the type of actual parameters passed in. For example: when using a function template with a double type, the compiler determines T to be a double type through the deduction of the actual parameter type, and then generates a code that specifically handles the double type, and the same is true for the character type.

2.4 Instantiation of template types

When a function template is used with parameters of different types, it is called instantiation of the function template. Template parameter instantiation is divided into: implicit instantiation and explicit instantiation.

1. Implicit instantiation: let the compiler deduce the actual type of the template parameter according to the actual parameter

template <typename T>

T Add(T & amp; a, T & amp; b)
{<!-- -->
return a + b;
}
int main()
{<!-- -->
int a = 1;
int b = 2;
cout << Add(a,b) << endl;
double c = 2.1, d = 3.1;
cout << Add(c, d) << endl;
//Add(a,c)
//This way of writing will report an error, because during the compilation process, when the compiler sees the instantiation of the template
//It will deduce the actual parameter type, if it sees that a is an int type, it will deduce T into an int, and if it sees that c is a double, it will deduce T into a double
//But there is only one template parameter T, so the compiler will make an error because it cannot distinguish the type of T.
//Solution: use explicit instantiation
return 0;

}

2.Explicit instantiation: Specify the actual type of the template parameter in <> after the function name

template <typename T>

T Add(T & amp; a, T & amp; b)
{<!-- -->
return a + b;
}
int main()
{<!-- -->
int a = 1;
double c = 1.1;
Add<int>(a, c);//explicit instantiation
return 0;
}

Note: If the types do not match, the compiler will try to perform an implicit type conversion, and if the conversion fails, the compiler will report an error.

2.5 Matching of template parameters

1. A non-template function can coexist with a function template with the same name, and the function template can also be instantiated as this non-template function

//Functions that specialize in processing int type addition
int Add(int a, int b)
{<!-- -->
return a + b;
}

//General template function
template <typename T>
T Add(T & amp; a, T & amp; b)
{<!-- -->
return a + b;
}

int main()
{<!-- -->
int a = 1;
int b = 1;
Add(a, b);//Match with non-template functions, use non-template functions
Add<int>(a, b);//Call the compiler specialized Add version
return 0;
}

2. For a non-template function and a function template with the same name, if other conditions are the same, the non-template function will be called first when calling and an instance will not be generated from the template. If the template can produce a function with a better match, then the template will be chosen

//Functions that specialize in processing int type addition
int Add(int a, int b)
{<!-- -->
return a + b;
}

//General template function
template <typename T1 , typename T2>
T1 Add(T1 & amp; a, T2 & amp; b)
{<!-- -->
return a + b;
}
int main()
{<!-- -->
\t
Add(1, 2); // Exact match for non-function template type, no need for function template instantiation
Add(1, 2.0); // The template function can generate a more matching version, and the compiler generates a more matching Add function based on the actual parameters
}

3. Template functions do not allow automatic type conversion, but ordinary functions can perform automatic type conversion

2.3 class template

//template<class T1, class T2, ..., class Tn>
//class class template name
//{<!-- -->
 // class member definition
//};
template <class T>
class Stack
{<!-- -->
public:
Stack(int capacity = 4)
{<!-- -->
_array = (T*)malloc(sizeof(T) * capacity);
if (_array == nullptr)
{<!-- -->
cout << "malloc fail" << endl;
exit(-1);
}
_capacity = capacity;
_size = 0;
}
Stack(const T & s)
{<!-- -->
_array = (T*)malloc(sizeof(T) * s._capacity);
if (_array == nullptr)
{<!-- -->
cout << "malloc fail" << endl;
exit(-1);
}
memcpy(_array, s._array, sizeof(T) * s._size);
_capacity = s._capacity;
_size = s._size;
}
\t
void Print()
{<!-- -->
for (int i = 0; i < _size; i ++ )
{<!-- -->
cout << _array[i] << ' ';
}
cout << endl;
}
~Stack()
{<!-- -->
free(_array);
_capacity = 0;
_size = 0;
}
private:
T*_array;
int_size;
int _capacity;
};
int main()
{<!-- -->
//Class template instantiation is different from function template instantiation. Class template instantiation needs to follow the class template name with <>, and then put the instantiated type in <>
//Only, the name of the class template is not the real class, but the result of instantiation is the real class
//Stack class name, Stack<int> is the type
Stack<int> s;
}

Summary

After learning memory management and templates, the previous knowledge of C ++ has almost been learned. Later we will enter the study of STL, which is the most important thing in C ++ learning.