C++ (pointers, smart pointers, exception handling)

Pointer

A pointer is a variable whose value is the address of another variable, that is, the direct address of a memory location. There is a big difference between pointers and references in C++: 1. The pointer stores the address of a variable, and the original variable of the reference root is essentially the same thing, which is an alias of the original variable; 2. There can be multiple pointers 3. The pointer can be empty and the declaration and definition can be separated. The reference cannot be NULL and must be initialized when it is defined; 4. The pointer can be changed after initialization, but the reference cannot be initialized. It can be changed again; 5. The size of the pointer is obtained by using the sizeof pointer, and the size of the variable pointed to by the sizeof reference is obtained; 6. When the pointer is passed as a parameter, a copy of the actual parameter is also passed to Formal parameter, the address pointed to by both is the same, but not the same variable, changing the pointer of this variable in the function will not affect the actual parameter, but the reference can; 7. The reference is essentially a pointer, which will also occupy 4 bytes of memory ( In the 64-bit compilation environment, the size of the pointer occupies 8 bytes, and in the 32-bit environment, the size of the pointer occupies 4 bytes, and the size of the memory occupied by the pointer is related to the compilation environment and has nothing to do with the number of bits of the machine); the pointer is a specific variable and needs to occupy storage Space (the specific situation needs to be analyzed in detail). According to the different characteristics of references and pointers, when passing parameters, different methods will be selected based on different situations: if you want to return the memory of local variables in the function, use pointers, and use pointers to open up memory and need to release it, otherwise it is easy to pass memory pointers Parameters are essentially passed by value (the pointer to the formal parameter has changed, but the pointer to the actual parameter will not change), and if our stack space is relatively small, we often use references, because there is no need to create temporary variables, the overhead is relatively small, and class objects are passed as parameters Always use references, because that’s the standard way of passing C++ class objects. The essence of pointer addition and subtraction is to move the pointed address. The step size of the movement is related to the pointer type, so when it comes to pointer addition and subtraction, be careful not to add more or less to point to an unknown memory address.
Distinguish the following pointer types:

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
int const*p/const int*p
int *const p

int *p[10] represents an array of pointers, emphasizing the concept of an array. It is an array variable with a size of 10. Each element in the array is a pointer variable pointing to an int type.
int (*p)[10] indicates an array pointer, emphasizing that it is a pointer. There is only one variable, which is a pointer type, but it points to an array of int type, and the size of this array is 10.
int *p(int) is a function declaration, the function name is p, the parameter is of type int, and the return value is of type int *.
int (p)(int) is a function pointer, emphatically it is a pointer. The function pointed to by the pointer has parameters of type int and the return value is of type int.
int const
p is a constant pointer, which is a pointer that points to a constant, so the address saved by the pointer can be changed, but the value pointed to by the pointer cannot be changed.
int *const p is a pointer constant, the value of the pointer itself (storage address) cannot be changed, and the value pointed to by the pointer can be changed (the constant pointer in the fifth edition of C++ Primer does not match what is described here)
Wild pointer and dangling pointer: Both are pointers to invalid memory areas (invalid here refers to “unsafe and uncontrollable”), access behavior will lead to undefined behavior. A wild pointer refers to a pointer that has not been initialized. In order to prevent errors, the general pointer should be assigned a value of nullptr or initialized when it is directly defined, so that there will be no illegal memory access. Dangling pointer: The memory originally pointed to by the pointer has been released (it is not vacated in time after free or delete, and needs to be vacated immediately after the release operation). Smart pointers are generally used in C++ to avoid the generation of dangling pointers.
In inheritance, pointers and references have a total of up-type conversion and down-type conversion. Up-type conversion is to convert a pointer or reference of a derived class into a pointer or reference of a base class. This is automatic and safe. Downcasting is to convert base class pointers or references to derived class pointers. This will not be done automatically because one base class is for several derived classes, and if you want to convert, you need to add dynamic type recognition technology to convert with dynamic_cast.
Differences between arrays and pointers: 1. Arrays are stored continuously in memory, opening up continuous memory space; 2. Use sizeof(array) to calculate the number of bytes in the array, and use sizeof§ to get the word of the pointer variable The number of sections rather than the memory capacity pointed to by p; 3. When using subscripts, the usage of arrays and pointers is the same, both are the original address plus the subscript value, but the original address of the array is the first address of the array is fixed, while The original address of the pointer is not fixed; 4. When passing parameters to the function, if the actual parameter is an array, the received formal parameter is the corresponding pointer, that is, the passed array is the first address instead of the entire array, which will improve efficiency.
The callback function is also implemented by using pointers. It is a function called through a function pointer. The pointer (address) of the function is passed as a parameter to another function. When this pointer is used to call the function it points to, it is Say this is a callback function. When a certain event occurs, the system or other functions will automatically call a defined function. The callback function is like an interrupt processing function, which is automatically called by the system when the set conditions are met, so the function declaration (with the definition There is a difference. The declaration only tells the compiler the location of the variable, and does not allocate memory space. The definition must allocate memory space where it is defined. Variables can be declared in multiple places, but can only be defined in one place. The function pointer must be declared with parentheses. The declaration is generally in the header file, and the function definition is generally in the source file), definition, trigger condition setting (in the function, the name of the callback function is converted into an address as a parameter, so that it can be used for system calls).

Smart pointer

What is a smart pointer: A smart pointer is a class that stores pointers to dynamically allocated objects and is responsible for automatically releasing dynamically allocated objects to prevent heap memory leaks. Dynamically allocated resources are handed over to a class object for management. When the life cycle of the class object ends, the destructor will be called automatically to release the resource (the use of ordinary pointers will cause memory leaks, that is, the problem of forgetting to release memory or secondary release) , Pay attention when using smart pointers when initializing, smart pointers are a template class, you can specify the type, the incoming pointer is initialized through the constructor, or you can use make_shared to initialize, but you cannot directly assign the pointer to a smart pointer
There are four commonly used smart pointers: shared_ptr, unique_ptr, weak_ptr, auto_ptr. The implementation principle of shared_ptr: the method of reference counter is adopted to allow multiple smart pointers to point to the same object. Whenever one more pointer points to the object, the internal reference count of all smart pointers pointing to the object + 1. Whenever a smart pointer points to an object, the reference count is -1. When the count is 0, the dynamically allocated resources will be released. 1. The smart pointer associates a counter with the object pointed to by the class, and the reference counter records how many class objects share the same pointer; 2. Every time a new object of the class is created, the pointer is initialized and the reference count is set to 1; 3. When When an object is created as a copy of another object, the copy constructor will copy the pointer and increase the corresponding reference count; 4. When performing an assignment operation on an object, the assignment operator will reduce the reference count of the object pointed to by the left operand (If it decreases to 0, delete the object), and increase the reference count of the object pointed to by the right operand; 5. When the destructor is called, the constructor reduces the reference count (if the reference count decreases to 0, delete the base object). shared_ptr actually maintains two parts of information, which are pointers to shared resources and control information of shared resources such as reference counts-maintaining a pointer to control information.

{<!-- -->
    std::shared_ptr<int> sptr = std::make_shared<int>(200);
    assert(sptr.use_count() == 1); // reference count is 1 at this time
    {<!-- -->
        std::shared_ptr<int> sptr1 = sptr;
        assert(sptr. get() == sptr1. get());
        assert(sptr.use_count() == 2); // sptr and sptr1 share resource, reference count is 2
    }
    assert(sptr.use_count() == 1); // sptr1 has been released
}
// Automatically release memory when use_count is 0

unique_ptr: It adopts exclusive ownership semantics. A non-empty unique_ptr always owns the resource it points to. Transferring a unique_ptr will transfer all ownership from the source pointer to the target pointer. The source pointer is set empty; so unique_ptr does not support ordinary copy and assignment, and cannot be used in STL containers; except for the return value of local variables (because the compiler knows that the object to be returned will be destroyed, it can still guarantee exclusive ownership); if To copy a unique_ptr, after the copy is over, the two unique_ptr will point to the same resource, causing multiple releases of the same memory pointer at the end and the program crashes. The life cycle of unique_ptr starts from the creation of the unique_ptr pointer until it leaves the scope, and destroys the object pointed to when it leaves the scope. This is the basic RAII idea. During the life cycle, the object pointed to by the smart pointer can be changed (create a smart pointer When specifying through the constructor, re-specifying through the reset method, releasing ownership through the release method, and transferring ownership through move semantics). The use cases are as follows:

{<!-- -->
    std::unique_ptr<int> uptr = std::make_unique<int>(200);
    std::unique_ptr<int> uptr1 = uptr; // compile error, std::unique_ptr<T> is move-only
    std::unique_ptr<int> uptr2 = std::move(uptr);
    assert(uptr == nullptr);
    // can also point to an array
    std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10);
    for (int i = 0; i < 10; i ++ ) {<!-- -->
        uptr[i] = i * i;
    }
    for (int i = 0; i < 10; i ++ ) {<!-- -->
        std::cout << uptr[i] << std::endl;
    }
}

weak_ptr: Weak references, reference counting has a problem that mutual references form a ring (circular reference). This is generally when using multiple shared_ptr pointers point to each other, thus forming a ring, similar to Deadlock phenomenon. In this case, the destructor of the object cannot be called normally, and the memory pointed to by the two pointers cannot be released (the reference counts of the two pointers are both reduced to 1 and passed because the mutual references are waiting for the other to release the counter first. 0, release by yourself), you need to use weak_ptr to destroy the circular reference, weak_ptr is a weak reference, a smart pointer introduced to cooperate with shared_ptr, it points to an object managed by shared_ptr without affecting the life cycle of the pointed object, and also That is to say, it will only reference, not count; if a piece of memory is referenced by shared_ptr and weak_ptr at the same time, when all shared_ptr is destroyed, no matter whether there is weak_ptr referencing the memory, the memory will be released, so weak_ptr does not guarantee the memory pointed to It must be valid, you need to use lock() to check whether weak_ptr is a null pointer before use. The reason why weak_ptr can solve the problem of circular references is because it will be automatically destructed as long as it goes out of scope.
auto_ptr: Mainly to solve the problem of “memory leaks when an exception is thrown”, obtain control of an object during construction, and release the object during destruction. In fact, we Create a local object of auto_ptr type. When the local object is destroyed, it will release the pointer space it owns, so there will be no memory leak. The constructor uses the explicit keyword to prevent the general pointer from being converted to auto_ptr, so the general pointer cannot be directly assigned to the auto_ptr object, and the auto_ptr constructor must be used to create the object but supports implicit type conversion between the pointer types it owns. Because auto_ptr will delete the pointer it owns when it is destructed (delete is used instead of delete[], so the array cannot be managed), so avoid multiple auto_ptr objects to manage the same pointer when using it. Generally use T* get() to obtain the pointer owned by auto_ptr; T* release() to release the ownership of auto_ptr and return all used pointers. auto_ptr has copy semantics, and the source object becomes invalid after copying, which will cause serious problems, while unique_ptr has no copy semantics. auto_ptr does not support copy and assignment operations, and cannot be used in STL standard containers. Elements in STL containers often support copy and assignment operations. In this process, auto_ptr will transfer ownership, so it cannot be used in ST, which is why auto_ptr is abandoned Main reason used, replaced by unique_ptr.

Exception handling

The exceptions in C++ have grammatical errors (compiler errors), such as undefined variables, mismatched brackets, and other errors that the compiler can find at compile time, and will know the location and cause of the error, and some are runtime Errors, such as array subscript out of bounds, insufficient system memory, etc. These problems need to be solved by introducing an exception handling mechanism. If the exception information is not processed, the program may crash. The exception handling mechanism of C++ mainly uses try, throw and The catch keyword is implemented. The use process is to execute the statement block wrapped by try first. If no exception occurs during the execution, it will not enter any statement block wrapped by catch. If an exception occurs, use throw to throw an exception, and then catch To capture, throw can throw information of various data types, you can use numbers, and you can also customize the exception class. Use catch(…) to catch any exception (not recommended).
Function exception declaration list: If you can know in advance what exceptions may occur in the function, you can point out the list of exceptions that can be thrown when declaring and defining int fun() throw(int,double,A,B ,C){...};, this way of writing indicates that the function may throw int, double or A, B, C type exceptions, if throw is empty, it means that no exception will be thrown, if not throw indicates that any exception may be thrown. In addition, C ++ also has the standard exception class exception. Some classes in the C ++ standard library represent exceptions are derived from exception, such as bad_typeid: use the typeid operator, if the operand is a polymorphic Class pointer, and the pointer value is NULL, an exception will be thrown. bad_cast: When using dynamic_cast to cast from a polymorphic base class object (or reference) to a derived class reference, if the conversion is unsafe, this exception will be thrown. bad_alloc: When using the new operator for dynamic memory allocation, if there is not enough memory, this exception will be thrown. out_of_range: When using the at member function of vector or string to access elements according to the subscript, if the subscript is out of bounds, this exception will be thrown.
Debugging of common errors coredump: coredump is a file called core generated under certain conditions when the program exits or terminates abnormally due to an exception or bug. This core file will record the memory and registers of the program at runtime State, memory pointers and function stack information, etc. Analysis of this file can locate the corresponding stack call information when the program is abnormal. Use gdb to debug coredump: gdb [executable file name] [core file name]
If an exception occurs in the constructor, the control will be transferred out of the constructor, and the object will not be executed in the constructor. At this time, the destructor will not be called, which will cause a memory leak. If unique_ptr is used to replace pointer class members, the problem of resource leaks during exceptions is avoided, because it is no longer necessary to manually release resources in the destructor. If an exception is thrown in the destructor and it is not caught, the destructor will not execute completely.

Reference materials: Axiu’s study notes, smart pointer.