First of all, we need to know what problem the smart pointer is used to solve. First, let's look at the following code.
void Func(int *p) { int *sp = new int; if (p == NULL) { throw std::exception("p is NULL"); } delete sp; }
This function has a big problem. if the parameter p passed in is empty, an exception will be thrown when an if statement is hit, and the program will be interrupted. Then the pointer sp points to the opened memory and does not release, resulting in memory leaks. What can be done to solve this problem?
Stack: System opens memory, system releases; heap: manually opens memory, manually releases. So if we can realize the mechanism of user allocation and system self-recovery in heap space, we don't need to worry about memory leak.
Intelligent pointer idea: Define a class to encapsulate the allocation and release of resources, complete the allocation and initialization of resources in the constructor, and clean up and release resources in the destructor, which can ensure the correct use and release of resources. (Resources refer to resources on the heap)
Look at the following code:
#include<iostream> template <typename T> class Smart_ptr { public: Smart_ptr(T *ptr) :mptr(ptr)//Constructor {} ~Smart_ptr()//Destructive function { delete mptr; mptr = NULL; } T& operator*()//* Operator overloading { return *mptr; } T* operator->()//-> Operator overloading { return mptr; } private: T *mptr; }; int main() { Smart_ptr<int>sp1(new int); *sp1 = 20; Smart_ptr<int>sp2 = sp1; }
Smart_ptr< int > SP2 = sp1; this code releases the same block of memory twice, resulting in program errors.
Here are four smart pointers auto_ptr
1. auto_ptr (abandoned)
Realization 1: Unmarked auto_ptr (ownership is unique)
#include<iostream> template <typename T> class Smart_ptr { public: Smart_ptr(T *ptr) :mptr(ptr) {} ~Smart_ptr() { delete mptr; mptr = NULL; } Smart_ptr(const Smart_ptr<T>& rhs)//copy constructor { mptr = rhs.mptr; rhs.Release();//Set the old pointer to NULL } Smart_Ptr<T>& operator=(const Smart_Ptr<T>& rhs)//Assignment operator overloading { if (this != &rhs) { delete mptr; mptr = rhs.mptr; rhs.Release();//Set the old pointer to NULL } return *this; } T& operator*() { return *mptr; } T* operator->() { return mptr; } private: void Release()const { (T*)mptr = NULL; } T *mptr; }; int main() { Smart_ptr<int>sp1(new int); Smart_ptr<int>sp2 = sp1; *sp1 = 20; }
Question: * SP1 = 20; the Sp1 pointer pointed to NULL already, and its dereference accessed the data in the reserve area. The program made a mistake.
Realization 2: The tagged auto_ptr (the release right is unique, the management right is not unique), that is, multiple auto_ptrs can manage the same piece of memory, but only one pointer can release the memory. How to determine which pointer can be released? A flag variable is added to the member variable. If flag is true, it has the right to release. If flag is false, it has no right to release. So the issue of transfer of release permission is involved. Generally speaking, the old smart pointer has permission, so does the new smart pointer. Old smart pointers don't have permissions, so new smart pointers don't.
Implementation of this function: new flag = old flag; old flag = false;
#include<iostream> template<typename T> class SmartPtr { public: SmartPtr(T* ptr) :mptr(ptr), flag(true) {} SmartPtr(const SmartPtr<T>& rhs) :mptr(rhs.mptr) { flag = rhs.flag; rhs.flag = false; } SmartPtr<T>& operator = (const SmartPtr<T>& rhs) { if (this != &rhs) { this->~SmartPtr(); mptr = rhs.mptr; flag = rhs.flag; rhs.flag = false; } return *this; } ~SmartPtr() { if (flag) { delete mptr; } mptr = NULL; } T& operator*() { return *mptr; } T* operator->() { return mptr; } private: T* mptr; mutable bool flag;//Does it have the right to release? }; void func(SmartPtr<int> sp)//true {} int main() { SmartPtr<int> sp1(new int);//false SmartPtr<int> sp2 = sp1;//false func(sp2); *sp1 = 20; return 0; }
Question: According to the first principle of management right, if the old smart pointer has the right to release, the new smart pointer will take the right to release, and the old right to release is false. So if this new smart pointer is only a temporary quantity, it will disappear with the advent of the lifecycle, and the managed memory will not be. Be rather baffling They were released.
void func(SmartPtr<int> sp)//true {} void test(SmartPtr<int> rhs) { std:: cout << *rhs << std::endl; } int main() { SmartPtr<int> p(new int(10)); SmartPtr<int> q(new int(20)); *p = 11; *q = 22; p = q; SmartPtr<int> w = p; test(w);//In the process of data transmission, w's right to release is transferred. std::cout << "w" << *w << std::endl;//Print a random value return 0; }
2. Scope_ptr (the sole right of administration and the sole right of release)
In auto_ptr, because of the design problem, the situation of accessing null pointer and wild pointer occurs when using overloadable function of copy constructor and assignment operator. In order to avoid these situations, scoped_str directly shielded the copy constructor and assignment operator overload function when it was designed.
#include<iostream> template<typename T> class Scope_Ptr { public: Scope_Ptr(T* ptr) :mptr(ptr){} ~Scope_Ptr() { delete mptr; mptr = NULL; } T& operator*() { return *mptr; } T* operator->() { return mptr; } private: Scope_Ptr(const Scope_Ptr<T>&); Scope_Ptr<T>& operator=(const Scope_Ptr<T>&); T* mptr; }; int main() { int* p = new int; Scope_Ptr<int> sp1(p); Scope_Ptr<int> sp2(p); return 0; }
Question: The principle of scoped_ptr is also "the sole management right and the sole release right". Because the overload function of copy constructor and assignment operator is shielded, it is impossible to make two or more pointers point to the same piece of memory in these two ways, but human operations such as sp1 and sp2 are still unavoidable. Initialize two scope_ptr s to point to the same piece of memory. When one of the pointers frees the memory, the other wants to modify the contents of the memory, the program crashes.
3. shared_ptr (the right of management is not unique, the right of release is not unique)
Shared_ptr is based on "reference counting". Multiple shared_ptrs can point to the same piece of memory, and they maintain a shared reference counter, which records the number of shared_ptr objects pointing to the same piece of memory. When the last shared_ptr object pointing to a piece of memory is also destroyed, the piece of memory is automatically destroyed. Shared_ptr solves the problem of program crash when multiple smart pointers point to a memory block.
Reference counter: We only need a reference counter object, which stores all information about memory references.
class Ref_Management { public: void AddRef(void* mptr)//increase { std::vector<Node>::iterator it = find(mptr);//Find out if this address exists if (it == vec.end())//If this address does not exist in the reference count table, add one { Node tmp(mptr, 1); vec.push_back(tmp); } else { (*it).ref++;//Existence, then add the reference count } } void DelRef(void* mptr)//delete { std::vector<Node>::iterator fit = find(mptr);//Find out if this address exists if (fit == vec.end())//If it finds none, then there is no throwing exception { throw std::exception("mptr is not exisit!"); } else { if (GetRef(mptr) != 0)//Judging the Reference Count of the Address { (*fit).ref--;//If the count is not zero-- } } } int GetRef(void* mptr)//Obtain { std::vector<Node>::iterator it = find(mptr);//Find out if this address exists if (it == vec.end())//If it finds none, then there is no throwing exception { throw std::exception("ptr is not exsit!"); } else { return (*it).ref;//Find the return reference count } } private: class Node; std::vector<Node>::iterator find(void* mptr)//Using iterator lookup { std::vector<Node>::iterator it = vec.begin(); for (it; it != vec.end(); it++) { if ((*it).addr == mptr) { break; } } return it; } class Node//Node Stereotypes { public: Node(void* pa = NULL, int r = 0) :addr(pa), ref(r){}//Constructor public: void* addr;//Deposit address int ref;//Reference Counting }; std::vector<Node> vec; };
template<typename T> class Shared_Ptr { public: Shared_Ptr(T* ptr) :mptr(ptr)//Constructor { addref(); } Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)//copy constructor { addref();//Increase reference count } Shared_Ptr<T>& operator = (const Shared_Ptr<T>& rhs)//Assignment operator overloading { if (this != &rhs)//Preventing self-assignment { delref();//Dereference count if (getref() == 0)//If the reference count is 0, delete the pointer { delete mptr; } mptr = rhs.mptr; addref(); } return *this; } ~Shared_Ptr() { delref();//Dereference count if (getref() == 0)//If the reference count is 0, delete the pointer { delete mptr; } mptr = NULL; } T& operator*() { return *mptr; } T* operator->() { return mptr; } private: void addref() { rm.AddRef(mptr); } void delref() { rm.DelRef(mptr); } int getref() { return rm.GetRef(mptr); } T* mptr; static Ref_Management rm;//Reference counter member variable object }; //Extra-class initialization of static member variables template<typename T> Ref_Management Shared_Ptr<T>::rm; int main() { int* p = new int; Shared_Ptr<int> sp1(p); Shared_Ptr<int> sp2(p); Shared_Ptr<int> sp3(p); Shared_Ptr<int> sp4(new int); return 0; }
Problem: Although shared_ptr solves the problem of "when multiple smart pointers point to the same memory, the deconstruction error" well, it also brings new problems. Shared_ptr cannot refer to each other, otherwise the managed memory blocks on the heap cannot be freed.