<> previously on
We know that in addition to static memory and stack memory , Each program also has a memory pool , This part of memory is called free space or heap . Programs use heaps to store dynamically allocated objects, that is, those allocated when the program is running , When dynamic objects are no longer used , Our code must explicitly destroy them .
stay C++ in , Dynamic memory management is accomplished by a pair of operators :new and delete,ne: Allocates a space for an object in dynamic memory and returns a pointer to the object ,delete Pointer to a dynamic exclusive , Destroy object , And release the memory associated with it .
Dynamic memory management often has two problems : One is forgetting to free memory , Can cause a memory leak ; One is to release memory when there is still a pointer to refer to it , A pointer to illegal memory is generated .
To make it easier ( More secure ) The use of dynamic memory , The concept of intelligent pointer is introduced . Smart pointers behave like regular pointers , The important difference is that it is responsible for automatically releasing the object it points to
<> The principle of intelligent pointer
RAII
: Using object life cycle to control program resources . Mainly through the object constructor to obtain the management of resources , Then the managed resources are released through the destructor . The principle is to entrust the responsibility of managing a resource to an object
//RAII template<class T> class SmartPtr { public: // Constructor to get resource management rights SmartPtr(T* ptr
) :_ptr(ptr) {} // Release resources through destructors ~SmartPtr() { if (_ptr) delete _ptr; } private: T*
_ptr; }; class A { private: int _a = 10; }; void test() { // Wrong writing int* ptr = new
int[10]; SmartPtr<int> sp(ptr); // Initialize now -- Bind resources as soon as you apply for them SmartPtr<int> sp2(new int)
; SmartPtr<A> sp3(new A); }
This is not a smart pointer object , Smart pointer should meet the following conditions
* realization RAII thought
* It is used in the same way as a pointer , For example, you need support * Dereference and -> operation
Overloads of the following operations should be added to the class
T* operator->() { return _ptr; } T& operator*() { return *_ptr; }
One of the differences between smart pointer and ordinary pointer is that smart pointer does not need to release space manually
void test() { // Smart pointer -- Compiler calls deconstruction to release resources automatically -- There is no memory leak SmartPtr<A> sp(new A); (*sp)._a =
10; sp->_a = 100; // Ordinary pointer -- Free memory manually int* p = new int; A* pa = new A; *p = 1; pa->_a
= 10; //return // Ending a normal pointer early can lead to a memory leak delete p; delete pa; }
<>C++ The use of intelligent pointer in standard library
The smart pointers in the library are divided into auto_ptr,unique_ptr, share_ptr
They all need to import header files #include <memory> To use
<>auto_ptr
auto_ptr It is a kind of defective intelligent pointer ( Disable )
#include <memory> using namespace std; void test() { auto_ptr<int> ap(new int);
auto_ptr<int> ap2(new int(2)); *ap = 10; *ap2 = 20; }
auto_ptr Pointer assignment will transfer resources , The purpose is to prevent multiple smart pointers from pointing to the same memory resource . But this design obviously does not meet our needs
Let's do a simple simulation auto_ptr, How does he transfer the right of resources at the bottom
// realization auto_ptr template<class T> class Auto_ptr { public: Auto_ptr(T* ptr) :_ptr(
ptr) {} ~Auto_ptr() { if (_ptr) delete _ptr; } T* operator->() { return _ptr; }
T& operator*() { return *_ptr; } Auto_ptr(Auto_ptr<T>& ap) :_ptr(ap._ptr) {
// Transfer of resource management right ap._ptr = nullptr; } Auto_ptr<T>& operator=(Auto_ptr<T>& ap) { if (
this!= &ap) { if (_ptr) delete _ptr; // Transfer of resource management right _ptr = ap._ptr; ap._ptr =
nullptr; } return *this; } private: T* _ptr; };
<>unique_ptr
unique_ptr Intelligent pointer solves the problem of resource management permission transfer by preventing copying ---- take unique_ptr The assignment operator function and the copy constructor are set to delete functions
void test() { unique_ptr<int> up(new int(10)); unique_ptr<int> up2(up);//error
unique_ptr<int> up3(new int(20)); up = up3; //error }
Cause of error : Copy construction and assignment overload functions are deleted functions
Underlying implementation :
template<class T> class Unique_ptr { public: Unique_ptr(T* ptr) :_ptr(ptr) {}
Unique_ptr(const Unique_ptr<T>& up) = delete; Unique_ptr<T>& operator=(const
Unique_ptr<T>& up) = delete; ~Unique_ptr() { if (_ptr) { delete _ptr; _ptr =
nullptr; } } private: T* _ptr; };
<>shared_ptr
shared_ptr yes C++11 An intelligent pointer provided by Zhongxin , It not only solves the problem of resource management authority transfer , It also provides reliable copy function
class A { public: int _a = 10; ~A() { cout << "~A()" << endl; } }; void test()
{ shared_ptr<A> sp(new A); shared_ptr<A> sp2(new A); shared_ptr<A> sp3(sp2);//ok
sp3= sp;//ok sp->_a = 100; sp2->_a = 1000; sp3->_a = 10000; cout << sp->_a <<
endl; cout << sp2->_a << endl; cout << sp3->_a << endl; }
Running results :
We find that as many resources as we apply for will be released , At this time sp and sp3 Share a resource , modify sp3 It's the same as modifying sp. So it's printed in the end 10000. That's a shared resource , How to release resources only once ?----
Reference count
We can go through it shared_ptr Interface provided use_count() Let's see , How many smart pointers are there to manage the same resource
void test() { shared_ptr<A> sp(new A); cout << sp.use_count() << endl;//1
shared_ptr<A> sp2(sp); cout << sp.use_count() << endl;//2 cout << sp2.use_count(
) << endl;//2 shared_ptr<A> sp3(new A); cout << sp.use_count() << endl;//2 cout
<< sp2.use_count() << endl;//2 cout << sp3.use_count() << endl;//1 sp3 = sp; sp3
= sp2; cout << sp.use_count() << endl;//2 cout << sp2.use_count() << endl;//2
cout<< sp3.use_count() << endl;//2 }
Running screenshot : The reason is that there is a tonal destructor in the middle , It's because when sp3 point sp Time ,sp3 The reference count for is 0, The destructor is called to free the resource . here sp The resources created are 3 Intelligent pointer to manage
graphic :
At the time of implementation , We should ensure that a resource corresponds to only one counter , Not every smart pointer has its own counter . So we can bind resources and counters together , The smart pointer to the same resource , The same counter is accessed
Member variable : A member variable should have two variables , They are the variables of the resource pointer _ptr And counter variables _countPtr, They are all variables of a pointer type
copy constructor : In the copy constructor , The pointer to the current object should point to the resource of the object to be copied , And copy its counter , Finally, the counter needs to be adjusted ++
Assignment operator overload
: We cannot judge whether two objects are equal , As long as the resources of the two objects are different , Then the assignment is required . In assignment , First, let the counter of the current object be checked –, If 0 Indicates that the resources of the current object are managed only by the current object , You need to release resources . Then change the current object pointer to the resource of the object to be copied , And copy its counter . Finally, the counter should be checked ++ operation
Destructor : A counter that determines the resources of the current object , Go ahead – operation , In determining whether the counter is 0, If 0 Will release the resources , Not for 0 Then do nothing
template<class T> class Shared_ptr { public: Shared_ptr(T* ptr) :_ptr(ptr) ,
_countPtr(new size_t(1))// Initialize to 1 {} Shared_ptr(const Shared_ptr<T>& sp) :_ptr(sp.
_ptr) , _countPtr(sp._countPtr) { // Counter accumulation ++(*_countPtr); } Shared_ptr<T>
operator=(const Shared_ptr<T>& sp) { if (_ptr != sp._ptr) { // Self decrement of self counter
// The counter is 0, The current object needs to release resources if (--(*_countPtr) == 0) { delete _ptr; delete _countPtr; }
_ptr= sp._ptr; _countPtr = sp._countPtr; ++(*_countPtr); } return *this; } ~
Shared_ptr() { // Counter self decrement if (--(*_countPtr) == 0) { delete _ptr; delete _countPtr;
_ptr= nullptr; _countPtr = nullptr; } } T& operator*() { return *_ptr; } T*
operator->() { return _ptr; } private: T* _ptr; size_t* _countPtr;// Counter pointer };
What we achieved shared_ptr In fact, there is no thread safety problem in the multi-threaded scenario ---- The reference counter pointer is a shared variable , When multiple threads are modified, the counter will be confused . This may cause resources to be released in advance or cause memory leakage
Let's take a look at the code , If it's safe , Then the final destructor should only be called once
void fun(const Shared_ptr<A>& sp, int n) { for (int i = 0; i < n; ++i)
Shared_ptr<A> copy(sp);// establish copy Smart pointer } void test() { Shared_ptr<A> sp(new A); int
n= 100000; thread t1(fun, ref(sp), n); thread t2(fun, ref(sp), n); t1.join();
t2.join(); }
Running results 1: We found that the destructor of the object was not called , This shows that there is a memory leak problem
Running results 2: Call the destructor twice , It means that a resource has been released twice .
We can provide an interface in the class to get the value of the counter
size_t getCount() { return *_countPtr; }
Then run it in the code and get the value of the counter , It was found that the value of the counter was not zero 0, So the destructor is not called
So we can lock the counter where we modify it . This lock cannot be a global variable lock , Resources cannot be affected by each other , Otherwise, when a resource is locked and modified , Another resource will be affected , This will affect the efficiency of code execution . A separate lock should be provided for each counter
here ++ Operation and – All operations are encapsulated
template<class T> class Shared_ptr { public: Shared_ptr(T* ptr) :_ptr(ptr) ,
_countPtr(new size_t(1))// Initialize to 1 , _mtx(new mutex) {} Shared_ptr(const Shared_ptr
<T>& sp) :_ptr(sp._ptr) , _countPtr(sp._countPtr) ,_mtx(sp._mtx) { // Counter accumulation
//++(*_countPtr); addCount(); } Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
{ if (_ptr != sp._ptr) { // Self decrement of self counter // The counter is 0, The current object needs to release resources //if (--(*_countPtr) ==
0) if (subCount() == 0) { delete _ptr; delete _countPtr; delete _mtx; } _ptr =
sp._ptr; _countPtr = sp._countPtr; addCount(); } return *this; } ~Shared_ptr() {
// Counter self decrement if (subCount() == 0) { delete _ptr; delete _countPtr; delete _mtx; _ptr
= nullptr; _countPtr = nullptr; _mtx = nullptr; } } T& operator*() { return *
_ptr; } T* operator->() { return _ptr; } size_t getCount() { return *_countPtr;
} size_t addCount() { _mtx->lock(); ++(*_countPtr); _mtx->unlock(); return *
_countPtr; } size_t subCount() { _mtx->lock(); --(*_countPtr); _mtx->unlock();
return *_countPtr; } private: T* _ptr; size_t* _countPtr;// Counter pointer mutex* _mtx; };
Running results : We found that in the multithreading scenario , It can also be released normally
<> Circular reference problem
shared_ptr In fact, there are some small problems , That is the problem of circular reference
Let's take a look at the following code first
struct ListNode { shared_ptr<ListNode> _next; shared_ptr<ListNode> _prev; int
_data; ~ListNode() { cout << "~ListNode()" << endl; } }; void test() {
shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode);
cout<< n1.use_count() << endl; cout << n2.use_count() << endl; n1->_next = n2;
n2->_prev = n1; cout << n1.use_count() << endl; cout << n2.use_count() << endl;
}
Running results : We found that there was no release of resources , The counter is also self increasing
graphic :
stay C++11 in , Specifically to solve this problem , A new intelligent pointer is introduced waek_ptr, This kind of pointer is called weak pointer
. In assignment or copy , The counter does not work ++. There is no real resource release during deconstruction .waek_ptr It cannot be used alone , Its biggest function is to solve the problem shared_ptr The problem of circular reference .
struct ListNode { weak_ptr<ListNode> _next; weak_ptr<ListNode> _prev; int _data
; ~ListNode() { cout << "~ListNode()" << endl; } }; void test() { shared_ptr<
ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); cout << n1.
use_count() << endl; cout << n2.use_count() << endl; n1->_next = n2; n2->_prev =
n1; cout << n1.use_count() << endl; cout << n2.use_count() << endl; }
Running results :
In our own way shared_ptr in , When we release our resources, we only use delete To release , In the way we apply for space, we do not use only new To apply for space, you may also use malloc Come and apply , It should be used at this time free To release . So we also need to add a delete device to the smart pointer
void test() { Shared_ptr<A> sp(new A[100]);// Call parsing will report an error }
The delegator can be realized by imitating function
template<class T> struct DeleteDel { void operator()(T* ptr) { delete ptr; } };
template<class T> struct FreeDel { void operator()(T* ptr) { free(ptr); } };
template<class T> struct DeleteArrDel { void operator()(T* ptr) { delete[] ptr;
} }; template<class T, class Del = DeleteDel<T>> class Shared_ptr { public:
Shared_ptr(T* ptr) :_ptr(ptr) , _countPtr(new size_t(1))// Initialize to 1 , _mtx(new mutex
) {} Shared_ptr(const Shared_ptr<T>& sp) :_ptr(sp._ptr) , _countPtr(sp._countPtr
) ,_mtx(sp._mtx) { // Counter accumulation //++(*_countPtr); addCount(); } Shared_ptr<T>
operator=(const Shared_ptr<T>& sp) { if (_ptr != sp._ptr) { // Self decrement of self counter
// The counter is 0, The current object needs to release resources //if (--(*_countPtr) == 0) if (subCount() == 0) { //delete
_ptr; // Freeing up space by deleting _del(_ptr); delete _countPtr; delete _mtx; } _ptr = sp._ptr;
_countPtr= sp._countPtr; addCount(); } return *this; } ~Shared_ptr() { // Counter self decrement
if (subCount() == 0) { //delete _ptr; // Freeing up space by deleting _del(_ptr); delete _countPtr;
delete _mtx; _ptr = nullptr; _countPtr = nullptr; _mtx = nullptr; } } T&
operator*() { return *_ptr; } T* operator->() { return _ptr; } size_t getCount()
{ return *_countPtr; } size_t addCount() { _mtx->lock(); ++(*_countPtr); _mtx->
unlock(); return *_countPtr; } size_t subCount() { _mtx->lock(); --(*_countPtr);
_mtx->unlock(); return *_countPtr; } private: T* _ptr; size_t* _countPtr;
// Counter pointer mutex* _mtx; Del _del; };
Technology