Shengzhuang pointer and resource management in C ++

zhaozj2021-02-08  285

Abstract: Author: Zhang Yan <-! #BeginEditable "summary" -> My favorite definition of resources is:. "Any obtain and thereafter released something in your program" Memory is a fairly obvious Example of resources. It needs to be obtained with New, use Delete to release. At the same time, there are many other types of resource file handles, important pieces, GDI resources in Windows, and so on. Extension of the concept of resources into the program is created, all objects that are released are also very convenient, regardless of whether the object is allocated in the stack or in the stack or in the stack or in the global role.

Text:

The robust pointer and resource management resources in C and their ownership I favorites are: "Anything that is obtained in your program and released later." Memory is an example of a fairly obvious resource. It needs to be obtained with New, use Delete to release. At the same time, there are many other types of resource file handles, important pieces, GDI resources in Windows, and so on. Extension of the concept of resources into the program is created, all objects that are released are also very convenient, regardless of whether the object is allocated in the stack or in the stack or in the stack or in the global role. For a given resource, it is an object responsible for releasing the resource or a code. All rights are divided into two levels-automatic and explicit (Automatic and Explicit), if an object's release is guaranteed by the mechanism of language itself, this object is automatically all. For example, an object embedded in other objects, and his clearing requires other objects to ensure when cleared. Outside objects are seen as owners embedded. Similarly, the release (destroying) of the object (destroyable as an automatic variable) created on the stack is to ensure that the control flow leaves the object defined by the object. In this case, it acts on the owner being seen as an object. Note that all auto-ownership is compatible with other mechanisms of language, including exceptions. Whether it is how to exit the action domain - normal flow control exits, a BREAK statement, a return, a goto, or a throw-automatic resource can be cleared. So far, so good! The problem is generated when introducing a pointer, handle, and abstraction. If you access an object through a pointer, such as the object allocated in the heap, C does not automatically pay attention to it. The programmer must explicitly release these resources with the appropriate program method. For example, if an object is created by calling New, it needs to be recycled with Delete. A file is opened with CreateFile (Win32 API), it needs to be turned off with CloseHandle. Critical section required for Entercritialsection requires LeaveCriticalSection exit, and so on. A "naked" pointer, file handle, or critical area is not owner to ensure that their final release. The premise of basic resource management is to ensure that each resource has their owner. The first rule is a pointer, a handle, a critical zone only has all anywaters when we block them into objects. This is our first rule: allocate resources in the constructor, release resources in the destructor. When you encapsulate all resources in accordance with the rules, you can guarantee that there is no resource leak in your program. This is very obvious when the encapsulating object is established or embedded in other objects in the stack. But what about those dynamic applications? Don't worry! Any dynamic application is considered to be a resource and is packaged in accordance with the methods mentioned above. The chain of this object package object has to be terminated in some place. It finally terminates the most advanced owner, automatic or static. These are guarantees for releasing resources for leaving the scope or program. Below is a classic example of the resource package. In a multi-threaded application, the problem with the sharing object between the thread is solved by contacting the critical area with such an object. Each customer who needs access to the shared resource needs to get a critical area. For example, this may be a method of implementation of the critical region under Win32.

class CritSect {friend class Lock; public: CritSect () {InitializeCriticalSection (& _critSection);} ~ CritSect () {DeleteCriticalSection (& _critSection);} privatevoid Acquire () {EnterCriticalSection (& _critSection);} void Release () {LeaveCriticalSection (& _critSection) } Critical_section _critsection;}; here is a smart part is that we ensure that each customer entering the critical regions can finally leave. The state of the "enter" critical area is a resource and should be packaged. Packages are typically referred to as a lock (LOCK). class Lock {public: Lock (CritSect & critSect): _critSect (critSect) {_ critSect.Acquire ();} ~ Lock () {_ critSect.Release ();} privateCritSect & _critSect;}; lock general usage as follows: void Shared: : Act () throw (char *) {lock lock (_critsect); // Perform action - May throw // Automatic Destructor of lock} Note No matter what happens, the critical area will guarantee the release of the language mechanism. There is also something that needs to be remembered - each resource needs to be packaged separately. This is because resource allocation is a very easy error, it is limited to resources. We will assume that a failed resource allocation will lead to an exception - in fact, this will often occur. So if you want to try to use a stone to play two birds, or apply for two forms of resources in a constructor, you may fall into trouble. As long as you think about what is successful in a resource allocation but another failure throws an exception. Because the constructor is not completed, the destructor cannot be called, and the first resource will leak. This situation can be very simple to avoid. Whenever you have a class that requires two or more resources, write two laughing packages embed them into your class. Each embedded construction ensures deletion, even if the package is not constructed. Smart Pointers We have not discussed the most common type of resource - with an operator NEW, which is later accessed with a pointer. Do we need to define a package class for each object? (In fact, the C standard template library has a template class called Auto_PTR, which is to provide this package. We will return to Auto_Ptr.) Let us start from an extremely simple, dull, but safe thing. Look at the SMART Pointer template class below, it is very strong, even unable to achieve. Template class sptr {public: ~ sptr () {delete _p;} t * operator -> () {return_p;} t const * operator -> () const {return_p;} protected: sptr () : _P (0) {} Explicit SPTR (T * P): _P (p) {} t * _p;}; why should I design the SPTR's constructor as protected? If I need to obey the first rule, then I must do this. Resources - here is an object of Class T - must be assigned in the package of the package. But I can't just call New T because I don't know the parameters of the Tie constructor.

Because in principle, each T has a different constructor; I need to define another package for him. The template will be large, for each new class, I can define a new package by inheriting SPTR and provides a specific constructor. Class Sitem: Public SPTR {public: Explicit Sitem (INT I): SPTR (New Item (i)) {}}; Is it true for every class with a Smart Pointer? To be honest - no! He is very teaching value, but once you learn how to follow the first rule, you can relax the rules and use some advanced technology. This technology is to make the SPTR's constructor into public, but just use it to do resource transfer. I mean to use the new operator as a parameter of the SPTR constructor, like this: SPTR Item (New Item (i)); this method significantly needs self-control, not just you, and includes each member of your program team. They must swear out to make the constructor do not use the constructor in other purposes. Fortunately, this rule is easy to strengthen. Just look for all new NEWs in the source file. Resource Transfer so far, we have discussed that the life cycle is in a single scope of resources. Now we have to solve a difficult problem - how to deliver resources between different scopes. This issue will become very obvious when you handle the container. You can create a string of objects, store them into a container, and then remove them, and arrange them in the event. In order to make this safe work - no disclosure - object needs to change its owner. A very obvious solution to this problem is to use Smart Pointer, whether it is still found before joining the container. This is how he works, you join the Release method to smart pointer: Template t * sptr :: release () {t * ptmp = _p; _p = 0; return ptmp;} Note in Release After calling, Smart Pointer is no longer the owner of the object - its inside pointer points to empty. Now, call Release must be a responsible person and quickly hide the returned pointer to the new owner object. In our example, the container calls RELEASE, such as this STACK example: void stack :: push (sptr & item) throw (char *) {if (_top == maxstack) throw "stack overflow"; _ arr [_top ] = item.release ();}; the same, you can also use the reliability of Release in your code. What should the corresponding POP method do? He should release resources and pray to call it is a responsible person and immediately make a resource to pass it to a smart pointer? This is not good. Strong Pointers resource management works on the content index (part of Windows NT Server, now Windows 2000), and I am very satisfied with this.

Then I started to think ... This method is formed in such a complete system. If you can put it in the language itself? Isn't it very good? I propose a strong pointer and a weak pointer (WEAK POINTER). A Strong Pointer will like many places and our SPTR - it will clear the object he pointed to by it after it exceeds its scope. Resource delivery is carried out in the form of strong pointer assignment. You can also have Weak Pointer, which use to access objects without all objects, such as referenced. Any pointer must be declared as strong or weak, and the language should come to pay attention to the specified of the type conversion. For example, you can't pass Weak Pointer to a place where Strong Pointer is required, but it can be. The PUSH method can accept a Strong Pointer and transfer it to the Strong Pointer in the Stack. The POP method will return a strong Pointer. The introduction of Strong Pointer will make garbage recovery into history. There is also a small problem here - modifying C standards is as easy as the president of the campaign. When I told my attention to Bjarne stroutrup, he looked at my eyes seems to be just like a thousand dollars. Then I suddenly thought of a thought. I can implement Strong Pointers themselves. After all, they all want to smart pointers. It is not a big problem for a copy constructor and overloading an operator. In fact, this is the auto_ptr in the standard library. It is important to give these operations to the syntax of a resource transfer, but this is not very difficult. Template sptr :: sptr (sptr & ptr) {_ p = ptr.release ();} template void sptr :: operator = (SPTR & PTR) {if (_p! = ptr._p) {delete _p; _p = ptr.release ();} This one of the reasons why the entire idea quickly succeeds is that I can pass this package pointer in a value! I have my cake and I can eat it. See this Stack's new implementation: Class Stack {ENUM {MaxStack = 3}; public: stack (): _top (0) {} void push (sptr & item) throw (char *) {if (_top> = MaxStack) Throw "stack overflow"; _ arr [_top ] = item;} sptr POP () {if (_top == 0) Return SPTR (); return_arr [--_ top];} privateInt _top; sptr _arr [maxStack]; Any attempts to assign his assignment to a normal pointer, because the type does not match. In addition, because the POP returns a strong Pointer in a value mode (there is no & symbol after the declaration of POP declaration), the compiler automatically performs a resource conversion when return. He called Operator = to extract an Item from an array, and the copy constructor passed him to the caller. The caller finally has an Item pointing to the strong Pointer to the POP assignment. I immediately realized that I have already on some things. I started to override the original code with a new method.

Analyzer (Parser) I have an old arithmetic operation analyzer in the past, which is written with old resource management. The role of the analyzer is to generate nodes in the analysis tree, and the node is dynamically allocated. For example, an Expression method of the analyzer generates an expression node. I don't have time to rewrite this analyzer with strong pointer. I am going to return the Strong Pointer to Node in the way of the EXPRESSION, TERM, and FACTOR methods. See the implementation of the Expression method: sptr Parser :: expness () {// PARSE A TERMSPTR PNode = Term (); etoken token = _scanner.token (); if (token == TPLUS || Token == Tminus) {// evr: = Term {(' ' | '-') Term} sptr PMULTINODE = New Sumnode (pnode); do {_scanner.accept (); sptr PRight = Term (); PMULTINODE-> AddChild (PRIGEN == TPLUS)); token = _scanner.token ();} while (token == TPLUS || token == Tminus); Pnode = Up_cast (PMULTINODE);} // OtherWise Expr: = Termreturn pnode; // by value!} The TERM method is called. His pass value returns a strong Pointer pointing to Node and immediately saves it to our own strong Pointer, pnode. If the next symbol is not a plus sign or a minus, we will simply return this sptriot to the value, which releases the ownership of Node. On the other hand, if the next symbol is a plus or minus number, we create a new Summode and immediately (directly pass) to save it into a strong Pointer of Multinode. Here, Sumnode is inherited from Multimode, and MulitNode is inherited from Node. The owner of the original Node is transferred to Sumnode. As long as they are separated by the plus sign, we constantly create Terms, we transfer these Term to our Multinode, and Multinode got all ownership. Finally, we will point to Multinode's Strong Pointer to point to Mode's strong pointer and return him to call. We need to explicitly map the Strong Pointers, even if the pointer is implicit. For example, a multinode is an Node, but the same IS-A relationship does not exist between SPTR and SPTR , because they are separated class (template instance) does not have inheritance relationships.

The UP-CAST template is as defined below: Template inline sptr Up_cast (sptr & from) {Return sptr (from.release ());} if you The compiler supports the newly joined standard member template, you can define a new constructor to accept a Class U for SPTR . Template Template sptr :: sptr (sprt & uptr): _p (uptr.release ()) {} This trick here is a child of U is not t When you don't compile success (in other words, you will only compile when U is-a t). This is because of UPTR. Release () method Returns a pointer to the U, and is assigned to _P, a pointer pointing to T. So if u is not a T, the assignment will cause a compilation time error. Std :: auto_ptr later I realized the Auto_PTR template in STL, which is my Strong Pointer. At that time, there were many implementation differences (Auto_PTR's Release method did not clear the internal pointer - your compiler's library is likely to use this kind of remarkable implementation), but finally before the standard is widely accepted. They were solved. TRANSFER SEMANTICS So far, we have been discussing the method of resource management in the C program. The purpose is to encapsulate resources into some lightweight classes and are responsible for their release. In particular, all resources allocated with new operators are stored and passed inside the Strong Pointer (Auto_Ptr) in the Standard Library. The keywords here are passed. A container can return a Strong Pointer to securely release resources through a pass value. The container can only save this resource by providing a corresponding Strong Pointer. Any way to assign the result to a "naked" pointer immediately by the compiler. Auto_PTR item = stack.pop (); // okitem * p = stack.pop (); // error! type mismatch. The object passed in a pass value has Value Semantics or called Copy Semantics. Strong Pointers is passed in a value - but can we say that they have Copy Semantics? not like this! The objects they point to must have not been copied. In fact, after passing, the source auto_ptr is not accessing the original object, and the target auto_ptr becomes the only owner of the object (but the old implementation of Auto_PTR is always maintained at the object of object even after release). Naturally we can call this new behavior as Transfer Semantics. The copy constructor and the assignment operator define the Transfer Semantics of Auto_PTR, which uses non-Const's Auto_PTR reference as their parameters. Auto_PTR (Auto_PTR & PTR); Auto_Ptr & Operator = (Auto_PTR & PTR); this is because they do have changed their source - deprived ownership of resources.

By defining the corresponding copy constructor and overload assignment operators, you can add Transfer Semantics to many objects. For example, resources in many Windows, such as dynamically established menus or bitmaps, can be encapsulated with classes with Transfer Semantics. The STRONG VECTORS standard library supports resource management only in Auto_PTR. Even the simplest container does not support Ownership Semantics. You may want to combine auto_ptr and standard containers to be used together, but it is not the case. For example, you may do this, but you will find that you can't use standard methods to index. Vector item = Autovector [ 0] We have no choice, only we can construct our own strong vector. The smallest interface should be as follows: template class auto_vector {public: explicit auto_vector (size_t capacity = 0); t const * Operator [] (size_t i) const; t * operator [] (size_t i); void assign SIZE_T I, AUTO_PTR & P); Void Assign_direct (Size_T I, T * P); Void Push_Back (Auto_Ptr & P); Auto_Ptr Pop_back ();}; You may find a very defense Sex design attitude. I decided not to provide an access to the left value of Vector, replaced it, if you want to set a value, you must use Assign or Assign_Direct method. My point is that resource management should not be ignored, and it should not be abused in all places. In my experience, a strong vector is often flooded by many Push_Back methods. Strong vector used is preferably a dynamic array Strong Pointers to implement: template class auto_vector {privatevoid grow (size_t reqCapacity); auto_ptr * _arr; size_t _capacity; size_t _end;}; grow a method of application Very large AUTO_PTR , all things are transferred from the old book group, exchanged, and delete the original array. Other implementations of Auto_Vector are very straightforward because all resource management complexity is in Auto_PTR. For example, the Assign method simply utilizes overloaded assignment operators to delete the original object and transfer resources to new objects: Void Assign (size_t i, auto_ptr & p) {_ arr [i] = p;} I have discussed the Push_Back and Pop_Back method. The PUSH_BACK method value returns an auto_ptr because it converts the ownership from Auto_Vector into Auto_PTR. The index access to Auto_Vector is implemented with the GET method of Auto_PTR, and Get returns an internal pointer.

T * Operator [] (size_t i) {return_arr [i] .get ();} There is no container if there is no Iterator. We need an Iterator to let Auto_Vector look more like a normal pointer vector. In particular, when we discard the Iterator, we need a pointer instead of auto_ptr. We don't want an Auto_Vector's Iterator to communicate resource conversion inadvertently. template class auto_iterator: publiciterator {public: auto_iterator (): _pp (0) {} auto_iterator (auto_ptr * pp): _pp (pp) {} bool operator = (auto_iterator! const & it) const {return it._pp! = _Pp;} auto_iterator const & operator (int) {return_PP ;} auto_iterator operator () {return _ pp;} t * operator * () {return_ppp -> get ();} privateAuto_ptr * _pp;}; we provide standard Begin and Endo to Auto_Vect to retrieve iterator: class auto_vector {public: typedef auto_vector itemarator; item; item begin () {Return _arr;} iterator end () {return_arr _end;}}; you may ask us to use resource management to reinforce each standard container? Fortunately, not; the fact is that the strong vector solves most of the ownership needs. When you place your objects safely into a Strong Vector, you can use all other containers to rearrange (Weak) Pointer. Imagine, for example, you need to sort some dynamically allocated objects. You save their pointers into a strong vector. Then you use a standard vector to save the WEAK pointer you have obtained from the Strong Vector. You can use the standard algorithm to sort this vector. This mediation vector is called Permutation Vector. Similarly, you can also use standard Maps, Priority Queues, Heaps, Hash Tables, and more. Code Inspection If you strictly follow the terms of resource management, you will not have any resource disclosure or two deleted places in trouble. You also reduce the chance of visiting the wild pointer. Similarly, follow the original rules, use Delete to remove the German pointer to the New, do not delete a pointer twice. You will not encounter trouble. But that is better attention to? These two methods have a big difference. It is easy to find a violation of resource management than the BUG looking for traditional methods. The latter only requires a code detection or a run test, and the former is hidden in the code and needs a deep inspection. Imagine that you have to make a memory leak check for a traditional code. The first thing, what you have to do is GREP All New in your code, and you need to find out what you have assigned a spatial point. You need to determine all execution paths that cause the deletion of this pointer. You need to check the BREAK statement, the process returns, exception.

The original pointer may assign another pointer, you have to do the same thing for this pointer. In contrast, the code implemented with resource management technology. You also check all New with grep, but this time you only need to check the neighboring calls: ● Is this a direct strong Pointer conversion, or in a functional body of a constructor? ● The returns of the call to know if it is saved to the object immediately, and whether there is an exception code in the constructor. ? ● If such a DELETE? Next, you need to find all the Release method with GREP and implement the same check. Different points are required to check, understand individual execution paths and only need to do some local inspections. Is this not reminding you that unstructured and structured programming is different? In principle, you can think that you can deal with Goto and track all possible branches. On the other hand, you can make your doubt to a piece of code. Localization is critical in both cases. Error mode in resource management is also easier to debug. The most common bug is trying to access a releaseless strong Pointer. This will result in an error and it is easy to track. Does the shared ownership find out for the resources in each program or a very easy thing to specify one owner? The answer is unexpected, yes! If you find some problems, this may indicate that there is a problem in your design. Another situation is that sharing ownership is the best or even the only choice. Shared responsibility assigns to the shared object and its customer (Client). A shared resource must keep a reference count for its owners. On the other hand, the owner must notify the shared object when the resource is released. The last release of resources is required to be responsible for free. The simplest share is the class REFCOUNTED: Class Refcounted {public: refcount (): _count;} void incrEfcount ()} void incrEfcount ()} void incrEfcount ()} void incrEfcount ()} void increfcount ()} void increfcount ()} DecrefCount () {return --_ count;} privateint _count;}; A reference count is a resource according to resource management. If you abide by it, you need to release it. When you realize this fact, it will become simple. Simple Follow the Rules-Reconfiguration Functions to get a reference count, release it in the destructor. There is even a refplanted Smart Pointer equivalent: Template class refptr {public: refptr (t * p): _P (p) {} refptr (refptr & p) {_ p = P._p; _p- > Increfcount ();} ~ refptr () {== 0) delete _p;} privatt * _p;}; Note Template t is not more than Become of Refcounted, but it must have increfcount and Decrefcount method. Of course, an easy-to-use REFPTR requires an overloaded pointer access operator. The Transfer Semantics is the service of the reader in the Refptr. The ownership network linked list is a very interesting example in resource management analysis. If you choose to become a chain (LINK), you will fall into the ownership of the recursive. Each link is owner of its successor, and the owner of the corresponding, remaining linked list.

转载请注明原文地址:https://www.9cbs.com/read-1963.html

New Post(0)