We know that there is no gc in C + +. The resources from new/malloc need to be released manually. At this time, some problems will occur. 1 Forget to release, 2 In case of abnormal security, these problems will lead to resource leakage and serious problems. Therefore, our smart pointer appears
Why do I need a smart pointer?
In fact, in the final analysis, in order to prevent memory leakage, prevent the loss of resources when we forget to release resources or throw exceptions between malloc and free
Memory leak
Let's look at this code first
int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("Divide by 0 error"); return a / b; } void f1() { int* p1 = new int; int* p2 = new int; int* p3 = new int; int* p = new int; try { cout<<div()<<endl; } catch(...) { div(); delete p; throw; } } int main() { try { f1(); } catch (exception& e) { cout << e.what() << endl; } system("pause"); return 0; }
It can be seen that although we set catch in the main function to receive the thrown exceptions, we new multiple objects, and each object may fail to open up, resulting in exceptions. At this time, we can't judge which object is thrown from, and we can't set catch for each object, That's why we have smart pointers to solve this problem
Use and principle of intelligent pointer
RAII
Let's look at such a piece of code
This is the principle of our smart pointer. We can see that the intelligent pointer is actually a template class. In fact, the class is Trusteed into the intelligent pointer by the object that new comes out, so that it can help us manage the release of resources. No matter the function ends normally or throws exception, it will cause the sp object's life cycle to arrive, and then call the destructor.
This is the idea of our RAII
Principle of intelligent pointer
// RAII + like a pointer template<class T> class SmartPtr { public: SmartPtr(T* ptr) :_ptr(ptr) {} ~SmartPtr() { if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; _ptr = nullptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; };
We need to overload the operator * and operator - > of the smart pointer to make it really use like a pointer
At this time, there is actually a problem, which is also the place of C + + pit. Let's see what happens if the following code is executed
int* sp1 = new int; int* sp2 = sp1
In fact, the two simple statements are to copy and divide sp2, so that sp1 and sp2 point to the int from new at the same time. At this time, because it is an intelligent pointer, the resource will be released automatically. At this time, the scene of releasing resources many times will appear
This is bound to go wrong, so how can we solve this problem?
std::auto_ptr
public: auto_ptr(T* ptr) :_ptr(ptr) {} auto_ptr(auto_ptr<T>& ap) :_ptr(ap._ptr) { ap._ptr = nullptr; } // ap1 = ap2 auto_ptr<T>& operator=(const auto_ptr<T>& ap) { if (this != &ap) { if (_ptr) delete _ptr; _ptr = ap._ptr; ap._ptr = nullptr; } return *this; } ~auto_ptr() { if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; _ptr = nullptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; };
We can see that in C++98, the solution is very simple. When sp1 is copied and constructed by sp2, both pointers point to int. at this time, set the previous sp1 to null, leaving only sp2. At this time, sp2 manages the resources of int, and the management right is transferred from sp1 to sp2. However, this method is not good, but it is only allowed, In fact, it is an early design defect, which is prohibited by general companies
bit::auto_ptr<int> ap1(new int); bit::auto_ptr<int> ap2 = ap1; *ap1 = 1; Dangling collapse
In the ap2 = ap1 scenario, ap1 is suspended, and an error will be reported when accessing. If you are not familiar with its features, you will be cheated
std::unique_ptr
// C++11 unique_ptr // Copy proof. Simple and rough, recommended // Defect: if there is a scene that needs to be copied, it cannot be used template<class T> class unique_ptr { public: unique_ptr(T* ptr) :_ptr(ptr) {} unique_ptr(unique_ptr<T>& up) = delete; unique_ptr<T>& operator=(unique_ptr<T>& up) = delete; ~unique_ptr() { if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; _ptr = nullptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; };
The defect of this method is also obvious, that is, when we need to copy, this method won't work
std::shared_ptr
From the name, we can see that shared pointers can share and point to an object. The purpose is to copy without the problem of hanging the auto pointer above. Reference counting is used here. Now the problem comes. Where should reference counting be added?
First of all, you can't add it inside the pointer. If you add it inside, each smart pointer will have a counter. It won't accumulate or add static when - it will affect other pointers, so what should we do?
In fact, our solution is to set count as a pointer so that sp1 and sp2 point to this pointer at the same time, so that count can be responsible for the common counting of sp1 and sp2
However, this operation is not thread safe, because when we count one object + +, it is possible to count another object + + at the same time. At this time, it is not safe to add it to a pointer
// C++11 shared_ptr // Reference count, can be copied // Defects: circular references template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr)//Initialization list :_ptr(ptr) , _pcount(new int(1))//When it is pointed to an object, the initial count is 1 , _pmtx(new mutex) {} shared_ptr(const shared_ptr<T>& sp)//copy construction :_ptr(sp._ptr) , _pcount(sp._pcount)//Introduction counter , _pmtx(sp._pmtx)//Introduce mutex { add_ref_count();//Count + 1 } // sp1 = sp4 shared_ptr<T>& operator=(const shared_ptr<T>& sp)//Overload operator= { if (this != &sp) { // Subtract the reference count, and release the resource if I am the last object to manage the resource release(); // I started managing resources with you _ptr = sp._ptr; _pcount = sp._pcount;//Shared counter _pmtx = sp._pmtx;//Shared lock add_ref_count();//Count + 1 } return *this; } void add_ref_count()//Lock + + before and after { _pmtx->lock(); ++(*_pcount);//For the pcount pointer + 1, if the integer cannot meet the desired effect, we need to make the pointer type so that sp1sp2 all point to it _pmtx->unlock(); } void release()//Resources cannot be released at the same time { bool flag = false;//Used to mark whether the lock can be released _pmtx->lock(); if (--(*_pcount) == 0)//When it is the last smart pointer to the object { if (_ptr) { cout << "delete:" << _ptr << endl;//Start resource release delete _ptr; _ptr = nullptr; } delete _pcount;//Release counter _pcount = nullptr; flag = true;//The lock needs to be released because the timer has decreased to 0 } _pmtx->unlock(); if (flag == true) { delete _pmtx;//Release lock _pmtx = nullptr; } } ~shared_ptr() { release(); } int use_count() { return *_pcount; } T* get_ptr() const { return _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; // Record how many objects share management resources together, and the last destructor releases resources int* _pcount; mutex* _pmtx; };
std::shared_ Thread safety of PTR
Circular reference
In fact, we defined share_ptr has solved many problems, but there is still an obvious problem. Let's take a look at this code first
ListNode* n1 = new ListNode; ListNode* n2 = new ListNode; n1->_next = n2; n2->_prev = n1; delete n1; delete n2;
This is the use of our general pointer, and the destructor will be called normally when running
wxy::shared_ptr<ListNode> spn1(new ListNode); wxy::shared_ptr<ListNode> spn2(new ListNode); cout << spn1.use_count() << endl; cout << spn2.use_count() << endl;
Let's look at this again. Change the pointer to a smart pointer. When we try to run this code, we find that there is a problem. spn1 and spn2 are not destructed. Why?
In fact, this is because it causes the problem of circular reference and restricts each other
We can see that when we first create two smart pointers to point to the object respectively, the counters are both 1. At this time, we point the two pointers to each other respectively, and the counters reach 2. At this time, we go out of the scope and start destruct, spn1 destruct, reference count-1, spn2 destruct, reference count-1. At this time, both counters become 1, and they are managed by the other party, This is a problem. My release is managed by you, and your release is managed by me. My life cycle needs to end, and your life cycle needs to end. However, we contain each other, and no one can end. At this time, it is impossible to deconstruct the object. There is a problem here
So how do we solve this problem?
C + + introduces another pointer, weak intelligent pointer
weak_ptr
// Strictly speaking, weak_ptr is not a smart pointer because it does not have RAII resource management mechanism // Dedicated to shared_ Circular reference of PTR template<class T> class weak_ptr { public: weak_ptr() = default;//Declare private weak_ptr(const shared_ptr<T>& sp)//The parameter is share_ptr :_ptr(sp.get_ptr())//Get pointer {} weak_ptr<T>& operator = (const shared_ptr<T>& sp) { _ptr = sp.get_ptr(); return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; }
We found that, in fact, wake_ In fact, PTR does not increase the count. Remove the shell share_ptr
// Circular reference spn1->_spnext = spn2; // Solution: use weak_ptr, do not increase the reference count spn2->_spprev = spn1; cout << spn1.use_count() << endl; cout << spn2.use_count() << endl;
Relationship between smart pointer in C++11 and boost
History of smart pointers
There is no gc (garbage collection period) in C + +, so it is a problem that the applied resources need to be released, especially when encountering abnormal security problems, which is particularly difficult to deal with
If you don't pay attention, there will be a memory leak. Memory leakage leads to less and less memory available to the program, and many operations in the program need memory. That will cause the program to be basically paralyzed, so we try to eliminate the memory leakage problem.
Therefore, the intelligent pointer based on RAII idea is developed, but since there is no gc pit, the intelligent pointer is introduced
The smart pointer has experienced more than ten years of development
Phase I:
auto is introduced in C++98 for the first time_ ptr, but auto_ The design of ptr has significant defects and is not recommended.
Phase II:
C + + officials didn't do anything in the next ten years. A group of cattle were angry and thought the C + + library was too simple, so they set up an unofficial community and wrote a library called boost. The smart pointer is rewritten in the boost library. Note that there are many other implementations in the boost library, scoped_ ptr/scoped_ _ array anti copy version
shared_ ptr/shared_ array # refers to the count version
weak_ ptr .
Phase III:
Smart pointer is introduced into C++11. It is slightly changed with reference to the implementation of boost. In fact, C++11 is similar to R-value reference, move statement, etc. it also refers to bootunique_ PTR (reference scoped_ptr)
shared_ ptr
weak_ ptr
Custom remover (understand)
In fact, the custom remover is to reformulate the destructor by using imitation functions and overloads for objects that cannot be recognized by smart pointers, such as arrays, malloc objects, fopen files, etc
// Custom remover -- (understand) #include<memory> class A { public: ~A() { cout << "~A()" << endl; } private: int _a1; int _a2; }; template<class T> struct DeleteArry { void operator()(T* pa) { delete[] pa; } }; struct Free { void operator()(void* p) { cout << "free(p)" << endl; free(p); } }; struct Fclose { void operator()(FILE* p) { cout << "fclose(p)" << endl; fclose(p); } }; int x4() { std::shared_ptr<A> sp1(new A); std::shared_ptr<A> sp2(new A[10], DeleteArry<A>()); std::shared_ptr<A> sp3((A*)malloc(sizeof(A)), Free()); std::shared_ptr<FILE> sp4(fopen("test.txt", "w"), Fclose()); return 0; }
RAII extended learning
// Lock management guard designed using RAII idea template<class Lock> class LockGuard { public: LockGuard(Lock& lock)//Constructor saves resources, locks and references to ensure the same lock :_lk(lock) { _lk.lock(); } ~LockGuard() { cout << "Unlock" << endl; _lk.unlock();//Destructor unlock } LockGuard(LockGuard<Lock>&) = delete; LockGuard<Lock>& operator=(LockGuard<Lock>&) = delete; private: Lock& _lk; // Note that this is a reference }; //void f() //{ // mutex mtx; // mtx.lock(); // // //func() / / it is assumed that func function may throw an exception. In this case, it is a deadlock // // mtx.unlock(); //} void f() { mutex mtx; LockGuard<mutex> lg(mtx); cout << div() << endl; // Suppose div function may throw exceptions }
The lock also uses the RAII idea to set up a guard to solve the problem
Supplement: memory leak
Memory leak:
1. What is a memory leak?
Memory embroidered building generally means that we have applied for a resource, but the resource is not used, but we forget to release it, or it is not released because of abnormal security and other problems
2. What is the harm of memory leakage?
If we apply for the memory and do not release it, if the process ends normally, the memory will also be released
Generally, if a program encounters a memory leak, it is OK to restart it. However, if it runs for a long time, the program that cannot be restarted will encounter a memory leak, which will do great harm, such as the operating system and services on the server. The harm is that these programs run for a long time, the unused memory is not released, and there are more and more memory leaks, resulting in the failure of many service operations because the container stores data, Memory is needed to open files, create sockets, send data, and so on
3. How to solve the memory leak problem
a. Be careful when writing code
b. Where it is difficult to handle, use smart pointers to manage and prevent in advance
c. If a memory leak is suspected or has occurred, you can use the memory leak tool to detect it and solve it afterwards. For example, valgrind is a powerful tool under Linux. You can also try other tools