Dynamic Memory and smart pointer
I. shared_ptr
The safest way to allocate and use dynamic memory is to call a standard library function named make_shared. This function allocates an object in the dynamic memory and initializes it. It returns the shared_ptr pointing to this object.
1 shared_ptr<int> p3 = make_shared<int>(42);2 shared_ptr<string> p4 = make_shared<string>(10, '9');3 shared_ptr<int> p5 = make_shared<int>();
If we do not pass a parameter, the object will initialize the value.
Shared_ptr has a reference counting variable that records the number of other shared_ptr objects pointing to the same object. If the counter changes to 0, the managed objects are automatically released.
Shared_ptr must be destroyed after it is useless. Otherwise, the program will still be correctly executed, but the memory will be wasted. Shared_ptr is retained after it is useless. One possibility is that you store shared_ptr in a container and then rearrange the container so that some elements are no longer needed.
In this case, you should ensure that you use erase to delete shared_ptr elements that are no longer needed. For example, the unique algorithm reassembles the input sequence, overwrites adjacent duplicate elements, and returns an iterator pointing to the position after non-repeating elements.
Ii. new and delete
New
By default, the dynamically allocated objects are initialized by default. Initialize the value after the type name with a pair of empty parentheses.
For the class type, no matter what form is used, the object will be initialized through the default constructor.
Tip: for the same reason as variable initialization, it is usually a good idea to initialize dynamically assigned objects.
1 auto p1 = new auto (obj); // correct 2 auto p2 = new auto (a, B, c); // Error
Since the compiler uses the initialize type to deduce the type to be allocated, auto can only be used when there is only one initializer in the brackets.
By default, if new cannot allocate the required memory space, it throws an exception of Type bad_alloc. We can change the new method to prevent it from throwing an exception.
1 int * p1 = new int; // if the allocation fails, new throws std: bad_alloc2 int * p2 = new (nothrow) int; // if the allocation fails, new returns a null pointer.
In this form of new, positioning is new (placement new ).
Delete
Delete p; // p must point to a dynamically allocated object or a null pointer
Generally, the compiler cannot tell whether a pointer points to a static or dynamically allocated object. Similarly, the compiler cannot tell whether the memory pointed to by a pointer has been released.
Although the value of a const object cannot be changed, it can be destroyed. To release a const dynamic object, you only need to delete the pointer to it:
1 const int * pci = new const int (1024); 2 delete pci; // correct
Tip: the built-in pointer must be displayed and released before the built-in pointer is destroyed.
Using new and delete to manage dynamic memory has three common problems:
1. Forget the delete memory.
2. Use released objects.
3. The same memory is released twice.
Tip: Stick to using only smart pointers to avoid all these problems.
When we delete a pointer, the pointer becomes invalid, but the pointer still stores the dynamic memory address on many machines.
A null pointer is a pointer to a memory that has previously stored data objects but is currently invalid. Essentially, it is similar to a non-initialized pointer.
There is one way to avoid the issue of the NULL pointer: the pointer is destroyed immediately after the memory is released.
If we need to retain the pointer, we can assign the nullptr to the pointer after the delete operation, so that it is clear that the pointer does not point to any object.
Iii. Combination of shared_ptr and new
If we do not initialize a smart pointer, it will be initialized as a null pointer.
The smart pointer constructor that accepts pointer parameters is explicit. Therefore, we cannot implicitly convert a built-in pointer to a smart pointer. We must use a direct initialization method to initialize a smart pointer:
1 shared_ptr <int> p1 = new int (1024); // Error 2 shared_ptr <int> p2 (new int (1024); // correct
For the same reason, a function that returns shared_ptr cannot implicitly convert a common pointer in its return statement:
1 shared_ptr <int> clone (int p) 2 {3 return new int (p); // error 4}
We must bind the shared_ptr display to a pointer to be returned:
1 shared_ptr <int> clone (int p) 2 {3 return shared_ptr <int> (new int (p); // correct 4}
By default, a normal pointer used to initialize a smart pointer must point to the dynamic memory, because the smart pointer uses delete by default to release the objects associated with it.
We can bind a smart pointer to a pointer pointing to other types of resources. However, to do so, we must provide our own operations to replace delete.
Cautions:
1. Do not mix common pointers and smart pointers
Shared_ptr can coordinate the analysis structure of an object, but it is limited to its own copy (also shared_ptr. This is why we recommend make_shared instead of new.
In this way, we can bind shared_ptr with the object while allocating it, thus avoiding inadvertently binding the same memory to multiple independently created shared_ptr.
1 void process (shared_ptr <int> ptr) 2 {3 // use ptr 4} 5 6 shared_ptr <int> p (new int (42); 7 process (p ); 8 int I = * p; // correct 9 10 int * x (new int (1024); 11 process (x ); // error 12 process (shared_ptr <int> (x); // valid 13 int j = * x; // undefined: x is an empty pointer!
In the above call, we pass a temporary shared_ptr to process. When the expression of the call ends, the temporary object is destroyed.
If this temporary variable is destroyed, the reference count is decreased, and the reference count is changed to 0. Therefore, when a temporary object is destroyed, the memory it points to will be released.
When a shared_ptr is bound to a common pointer, we assign the memory management responsibility to this shared_ptr.
Once this is done, we should no longer use the built-in pointer to access the memory pointed to by shared_ptr.
2. Do not use get to initialize another smart pointer or assign a value to the smart pointer.
The smart pointer type defines a function named get, which returns a built-in pointer pointing to an object managed by the smart pointer.
This function is designed for this scenario: we need to pass a built-in pointer to code that cannot use smart pointers.
The code that uses the pointer returned by get cannot delete this pointer.
Although the compiler does not provide error information, it is wrong to bind another smart pointer to the pointer returned by get:
1 shared_ptr <int> p (new int (42); 2 int * q = p. get (); 3 {4 shared_ptr <int> (q); // undefined: two independent shared_ptr points to the same memory 5} 6 int foo = * p; // undefined: the memory pointed to by p has been released
Tip: get is used to pass the pointer access permission to the code. You can use get only when the Code does not delete the pointer.
In particular, never use get to initialize another smart pointer or assign a value to another smart pointer.
3. Other shared_ptr operations
We can assign a new pointer to a shared_ptr:
1 p = new int (1024); // Error 2 p. reset (new int (1024); // correct
The reset will update the reference count. Reset members are often used with unique to control multiple shared_ptr shared objects.
Before changing the underlying object, we check whether we are the only user of the current object. If not, make a new copy before the change:
1 if (! P. unique () 2 p. reset (new string (* p); // we are not the only user; assign a new copy 3 * p + = newVal; // we are the only user and can change the object value.
Iv. er)
Similar to managing dynamic memory, we can usually use similar technologies to manage classes that do not have well-defined destructor.
For example, if we are using a network library that both C and C ++ use, the code for using this library may be as follows:
1 struct destination; // indicates what we are connecting 2 struct connection; // use the information required for connection 3 connection connect (destination *); // open connection 4 void disconnect (connection); // close the given connection 5 void f (destination & d/* Other parameters */) 6 {7 // get a connection; remember to close it after use 8 connection c = connect (& d); 9 // use connection 10 // if we forget to call disconnect before f exits, cannot close c 11}