Valid C ++ Clauses 13, 14, and valid clauses
Claxiii: object-oriented resource management
From this clause, we will go to the Resource Management Section. Resource management is often a difficult and top priority for large projects. Some programming specifications are the focus of resource management standards as high priorities.
The best way to manage resources is to prevent them. A good way to prevent them is to try not to use the native pointers of C/C ++. These pointers are like ghosts, and they are "forgotten ", is a hidden risk. When the project is small, these risks cannot be seen, but when developing a product with tens of thousands of users, the server simultaneously runs the code containing risks for many people, this risk will erupt, resulting in insufficient memory and crash.
int *p = new int(); … delete p;
This is of course OK, and the memory can be recycled. But if the process in the middle is more than dozens of rows, can you remember to delete p?
Some may do this. After each new entry, write the delete statement first and insert the code in the middle. However, if this happens:
int* GetResource() { return new int(10); }
This function is responsible for generating resources and returning them to the previous layer. Because the resources are used at the previous layer, you cannot directly delete them in the function, which is troublesome. Programmers can remember this memory at any time and release it immediately when they no longer need it. However, everything that may go wrong will really go wrong. If there are more code, the memory is not so good, and it is easy to forget delete.
Is there any good way for us to manage resources with ease? This involves the issue of smart pointers. Common smart pointers include auto_ptr in the memory header file and shared_ptr In the boost library. The two have different implementation mechanisms, but their functions are similar, that is, they can automatically manage resources. The idea Is that RAII (Resources Acquisition Is Initialzeation Resources are obtained at the time of initialization ).
The following is an example of an image:
Auto_ptr ap (new int (10 ));
Or:
Shared_ptr sp (new int (10 ));
We can see that there is no native pointer here, but the new memory is directly handed over to a "manager ". This manager can be auto_ptr or shared_ptr. Their existence makes it possible for programmers to really forget the delete operation. When the lifecycle of the ap or sp ends, they release the resources, return it to the system. For example:
Void fun () {auto_ptr
Ap (new int (10); * ap = 20;} // ap is automatically reclaimed after fun () ends.
The difference between auto_ptr and shared_ptr is that the management method is different.
The auto_ptr manager is a very domineering boss. He only wants to manage resources and does not allow other managers to intervene. Otherwise, he will exit the management and hand over the resource to the other party, I will never touch it again.
For example:
Auto_ptr
Boss1 (new int (10); // At this time boss1 manages this resource auto_ptr
Boss2 (boss1); // at this time, boss2 needs to intervene, so boss1 is very angry, so it throws the management right to boss2, and you will be done. So boss2 holds this resource, while boss1 no longer holds (some hold null ).
So at the same time, only one auto_ptr can manage the same resource.
It is necessary to explain this sentence here, for example:
auto_ptr
boss1(new int(10)); auto_ptr
boss2(new int(10));
Does boss1 still hold resources? The answer is yes, because new runs twice, although the initial values in the memory space are the same, but the addresses are different, so it is not the same resource.
Again, in order to clarify the problem, let the native pointer appear again here
int *p = new int(10); auto_ptr
boss1(p); auto_ptr
boss2(p);
Does boss1 still hold resources?
The answer is that when the program runs to the third sentence, the assertion failed Red Cross pops up and the program crashes. Why? Because p points to the same resource, boss1 must take action after boss2. In the third sentence, the constructor of boss2 is called. However, boss2 In the constructor recognizes that the resources pointed to by p are occupied, so assertion failed is triggered.
In fact, this native pointer and smart pointer mix program is very bad, if you do not use native pointer, the above crash will not happen.
According to the idea of auto_ptr, It is not complicated to write this class for interested readers:
# Ifndef MY_AUTO_PTR_H # define MY_AUTO_PTR_H // custom smart pointer # include
Using namespace std; template
Class MyAutoPtr {private: T * ptr; static void swap (MyAutoPtr & obj1, MyAutoPtr & obj2) {// The variable std: swap (obj1.ptr, obj2.ptr) ;}; public: explicit MyAutoPtr (T * p = NULL): ptr (p) {} MyAutoPtr (MyAutoPtr & p) {ptr = p. ptr; p. ptr = 0;} MyAutoPtr & operator = (MyAutoPtr & p) {if (& p! = This) {MyAutoPtr temp (p); swap (* this, temp);} return * this ;}~ MyAutoPtr () {delete ptr; ptr = 0;} T & operator * () const {return * ptr;} T * operator-> () const {return ptr ;} T * get () const {return ptr;} friend ostream & operator <(ostream & out, const MyAutoPtr
& Obj) {out <* obj. ptr; return out ;};# endif/* MY_AUTO_PTR_H */MyAutoPtr
Because auto_ptr often leads to management delivery, be careful when using it as a form parameter:
If a function is:
void fun(auto_ptr
ap){}
Call fun (main_ap) in the main function );
Main_ap will lose management right.
Because of this feature,Therefore, auto_ptr does not support STL containers because containers require "replicable", but auto_ptr can only have one master privilege. In addition, auto_ptr cannot be used for arrays, because the internal implementation uses delete instead of delete [].
Shared_ptr, as its name implies, is a manager of sharing and cooperation. Its internal implementation refers to the counting type. Every time multiple references are made, the counting value will be + 1, at the end of each referenced lifecycle, the Count value is-1. When the Count value is 0, the memory is not managed by the manager, the last manager that manages the memory will release it.
Return to our previous example:
Shared_ptr
Boss1 (new int (10); // At this time boss1 manages this resource shared_ptr
Boss2 (boss1); // boss2 and boss1 manage this resource
Note that boss1 does not hand over the management right at this time. Adding boss2 only affects the counting pointer inside the object, and it does not happen externally, you can use boss1 and boss2 to manage resources.
The shared_ptr I wrote below is also very simple (note that swap functions should be written by myself and cannot be called by the system, otherwise they will fall into the infinite recursion of the value assignment operator ):
# Ifndef MY_SHARED_PTR_H # define MY_SHARED_PTR_H # include
Using namespace std; template
Class MySharedPtr {private: T * ptr; size_t * count; static void swap (MySharedPtr & obj1, MySharedPtr & obj2) {std: swap (obj1.ptr, obj2.ptr); std :: swap (obj1.count, obj2.count);} public: MySharedPtr (T * p = NULL): ptr (p), count (new size_t (1) {} MySharedPtr (MySharedPtr & p): ptr (p. ptr), count (p. count) {++ * p. count;} MySharedPtr & operator = (MySharedPtr & p) {if (this! = & P & (* this). ptr! = P. ptr) {MySharedPtr temp (p); swap (* this, temp);} return * this ;}~ MySharedPtr () {reset ();} T & operator * () const {return * ptr;} T * operator-> () const {return ptr;} T * get () const {return ptr;} void reset () {-- * count; if (* count = 0) {delete ptr; ptr = 0; delete count; count = 0; // cout <"delete" <endl ;}} bool unique () const {return * count = 1 ;}size_t use_count () const {return * count ;} friend ostream & operator <(ostream & out, const MySharedPtr
& Obj) {out <* obj. ptr; return out ;};# endif/* MY_SHARED_PTR_H */MySharedPtr
Well, let's summarize:
To prevent resource leaks, use the RAII idea to obtain resources in the constructor and release resources in the destructor.
The two commonly used RAII classes are shared_ptr and auto_ptr, but the former is generally a better choice, because its copy behavior is more intuitive. If auto_ptr is selected, the copy action will point it to null.
Clause 14: copying actions in resource management
First, let's look at an example:
#include
using namespace std;class Lock{public: explicit Lock(int* pm): m_p(pm) { lock(m_p); } ~Lock() { unlock(m_p); }private: int *m_p; void lock(int* pm) { cout << "Address = " << pm << " is locked" << endl; } void unlock(int *pm) { cout << "Address = " << pm << " is unlocked" << endl; }};int main(){ int m = 5; Lock m1(&m);}
This is a lock and unlock operation that imitates the example in the original book.
The running result is as follows:
This is as expected. When m1 acquires the resource, it is locked. After the m1 lifecycle ends, it also releases the resource lock.
Note that the Lock class has a pointer member. If the default destructor, copy constructor, and assign value operator are used, there may be serious bugs.
We may add a sentence to the main function as follows: <喎? kf ware vc " target="_blank" class="keylink"> VcD4NCjxwcmUgY2xhc3M9 "brush: java;"> int main () {int m = 5; Lock m1 (& m); Lock m2 (m1 );}
It can be seen that the lock is released twice, which leads to a problem. The reason is that the Destructor is called twice and two Lock objects are generated in the main () function, namely m1 and m2, and Lock m2 (m1) this statement makes m2.m _ p = m1.m _ p, so that the two pointers point to the same resource. According to the principle of the first destructor of the generated object, m2 is first destructed, and its destructor is called to release the resource lock. However, the released message is not notified to m1, therefore, m1 will release the resource lock in the subsequent destructor.
If the release is not a simple output, but the memory is actually operated, the program will crash.
In the final analysis, the program uses the default copy constructor (of course, if the assignment operation is used, the same bug will also occur ), the solution is to properly flatten the copy constructor (and the value assignment operator ).
The first method is simple and intuitive. It simply prevents programmers from using statements like Lock m2 (m1) and reports a compilation error. This can be solved by writing a private copy constructor and a value assignment operator declaration. Note that you only need to write the statement here (see clause 6 ).
class Lock{public: explicit Lock(int* pm): m_p(pm) { lock(m_p); } ~Lock() { unlock(m_p); }private: int *m_p; void lock(int* pm) { cout << "Address = " << pm << " is locked" << endl; } void unlock(int *pm) { cout << "Address = " << pm << " is unlocked" << endl; }private: Lock(const Lock&); Lock& operator= (const Lock&);};
In this way, the compilation will not pass:
Of course, you can also write an Uncopyable class as the base class, just like in the book. In the base class, the copy constructor and value assignment operations are written as private (to prevent the base class from being generated, but to allow the derived class to generate objects, you can change the modifier of constructor and destructor to protected ).
Then
Class Lock: public Uncopyable
{...}
That's OK.
The second method is to use shared_ptr for resource management (see the previous clause). But there is another problem. I want to call the Unlock method after the lifecycle ends, in fact, the delimiters in shared_ptr can help us.
As long:
class Lock{public: explicit Lock(int *pm): m_p(pm, unlock){…}private: shared_ptr
m_p;}
In this way, after the lifecycle of the Lock Object ends, the unlock can be automatically called.
On the basis of clause 13, I changed the custom shared_ptr so that it also supports the delete operation. The Code is as follows:
# Ifndef MY_SHARED_PTR_H # define MY_SHARED_PTR_H # include
Using namespace std; typedef void (* FP) (); template
Class MySharedPtr {private: T * ptr; size_t * count; FP Del; // declare the static void swap (MySharedPtr & obj1, MySharedPtr & obj2) {std :: swap (obj1.ptr, obj2.ptr); std: swap (obj1.count, obj2.count); std: swap (obj1.Del, obj2.Del);} public: MySharedPtr (T * p = NULL ): ptr (p), count (new size_t (1), Del (NULL) {}// Add the constructor MySharedPtr (T * p, FP fun ): ptr (p), count (new size_t (1), Del (fun) {} MySharedPtr (MyShare DPtr & p): ptr (p. ptr), count (p. count), Del (p. del) {++ * p. count;} MySharedPtr & operator = (MySharedPtr & p) {if (this! = & P & (* this). ptr! = P. ptr) {MySharedPtr temp (p); swap (* this, temp);} return * this ;}~ MySharedPtr () {if (Del! = NULL) {Del () ;}reset () ;}t & operator * () const {return * ptr;} T * operator-> () const {return ptr ;} T * get () const {return ptr;} void reset () {-- * count; if (* count = 0) {delete ptr; ptr = 0; delete count; count = 0; // cout <"delete" <endl ;}} bool unique () const {return * count = 1;} size_t use_count () const {return * count;} friend ostream & operator <(ostream & out, const MySharedPtr
& Obj) {out <* obj. ptr; return out ;};# endif/* MY_SHARED_PTR_H */MySharedPtr with the deleteer
The third method is to copy the bottom resource, that is, to convert the original shallow copy to a deep copy. You need to define the copy constructor and the value assignment operator on your own. This is also mentioned in the previous terms. Here, the number of lock counts is actually increased by 1 during the copy operation. In the destructor, the number of lock counts is-1, unlock if it is reduced to 0 (in fact, the idea is similar to shared_ptr for resource management)
The fourth method is to transfer the control of the bottom resource. This is the job of auto_ptr. In the second method, replace shared_ptr with auto_ptr.
Summary:
To copy a RAII object, you must copy the resources it manages. Therefore, the copying action of the resource determines the copying action of the RAII object.
The common and common RAII class copying behavior is: to suppress copying, to execute the reference notation (shared_ptr), or to transfer the bottom Resource (auto_ptr)