Thinking by a AUTO_PTR source code Kyle
CPPCN Copyright If I ask you, Auto_PTR is the most critical place, or where it is the most different place in general pointers, you will say it is an object, it is closely related to the resources you have, when you die It will also release the resources you have. Very good, it will have this feature, all have to be in the contribution of the destructor, in the destructive function, it will work after a good treatment, which is very good to avoid resource leakage. Of course, the cause of introducing auto_ptr is not so simple, because many resource leaks are mainly due to our careful intentions, so as long as we are careful, there will be some resource leaks that should not happen. What is the reason for us to introduce Auto_PTR? By the way, you must also think of it, it is when it is handled. OK, come and see the following example: void foo () {classa * ptr = new classa; try {/ * This place may throw an exception * /} catch (...) {delete ptr; throw;} delete PTR; } The above example uses a general pointer that you will find that it is more complex to prevent resource leaks, and must be released in each Catch, and there is a lot of catch. God, this is a disaster, it will make our code very long, not easy to maintain, if you forget one, you will have inexplicable errors. Since the general pointer prevents the resource leaks, this is so cumbersome. If there is any way to make us not worry about the release of resources, because in an abnormal occurs, the stack is retracted, so we don't have to worry as a part of the pointer itself. Not destroyed, in this case, we simply build a pointer to the elephant, like the following: Template class auto_ptr1 {private: t * ap; public: ...... .. ~ auto_ptr (); // Resource release} When the pointer When destroyed, the destructive function will inevitably, then the release of resources in the destructor is not OK, huh, how, is it very simple? Indeed, the entire logic is indeed very simple, but if we think about the characteristics of this pointer, we will find that there is a difficult problem waiting for us to solve. Let's take a look at what it will encounter. Since it will automatically release the resources owned by the parsive function when it is destroyed in Auto_PTR, then AUTO_PTR is only exclusive for resources, that is, a resource can only be directed by an Auto_PTR. This should be very well understood, suppose there are two auto_ptr points to the same resource, then when one is destroyed, the other will point out? This pointer is often the most dangerous. In this case, how can we guarantee this exclusiveity, it is also very simple. When assigning and replicating the pointer, the deprivation of the ownership of the resource is to be tough. Just like this: auto_ptr P (new int (20)); auto_ptr q; q = p; // p has lost its ownership of resources, Q is now P's owner
For this general situation, the problem seems to have been solved, let us look at a special but reasonable situation:
Auto_PTR foo () {auto_ptr P (new int (20)); return p;} int main () {auto_ptr q (foo ()); return 0;} You think the above How is the situation, it is reasonable because it achieves the smooth handover of resources, but you think auto_ptr q (foo ()); how should this sentence call success? In order to explain this problem, you need to talk about the left and right values and the problem of temporary objects. Maybe you will say that the left value should be the variable that can change, and the right value is of course the variable that cannot be changed! right? For a little bit, the left value is the variable that can be referenced. The popular point is the variable of the name. You must think of something, yes, there is no name, even if you don't know, Because it is not created by you, the compiler will identify it internally, but you don't know, the temporary variable is not left, but the right value. You may ask, is the constant? According to the definition, it has a name, of course, the left value. Therefore, the left value is not "can be modified". But is there any relationship with the left and right values and parameters? I want to tell you: Yes, and quite close, because standard C specifies: If the argument to the type referenced is the right parametery, the number must be guaranteed to be a const reference. OK, now let us return to the original problem, because the foo () is returned, the compiler will inevitably generate a temporary object, that is, auto_ptr q (foo ()); this sentence is the structure of Q The parameter introduced in the function is a right value, so if you want to call this success, its prototype must be like this: auto_ptr (const auto_ptr &); but does this do? Obviously, it is not possible, because we have to deprive the original meaning of the ownership of resources, if you use const references, it is unable to deprive because you can't modify it. You may think of another way, we only need to modify the core data field with mutable, even if it is constant, you can modify its core data. This method seems to be good, but if we are considering the following situation, you may change your opinion, assume that there is a const auto_ptr, if we assign it to another auto_ptr, what should you say what should be? Of course, Of course, it should be prohibited, because you should not try to change a const object, that is, it is forbidden to deprive a const auto_ptr on the ownership of resources. But if you follow your thoughts, this change can be achieved, so you should disperse the idea of Mutable. So happened? Of course, it is not easy to think about it, please see the simple example below: Class X {Private: int value; public: x (int v = 0) {value = v;} x (x & a) {value = a. Value; a.Value = 0;} int set (int V) {value = v; return value;} Friend Ostream & Operator << (Ostream & OS, Const X) {os << x.Value << endl;}} ;
X f () {x a (100); returnif;} int main () {x c ()); cout << c; return 0;} Some of this example and what we have encountered c (f ()); this sentence is unable to call success, and cannot change the reference parameters of the replication constructor to const because we want to modify the parameters. OK, we use this simple example to solve the problems we have encountered. Since some programs we have already thought of can't reach our goal, how do we do it, right, we can use type conversion functions. Let me help you organize you: 1. We should first define a type of conversion layer, which should be the same as the core data of X, for example: struct y {int val; y {int val; y (int V): val (v) {}}; with this conversion layer, We can first turn the function f () to the temporary object generated by a conversion function from X to Y to Y. Then the structure of the object from Y to X's conversion function. At this point, all issues are resolved. Let's take a look at the specific method. 2. Add a type conversion function from X to Y. As follows: Operator y () {y y y y y y; return y;} 3. Add a type conversion function from Y to X, that is, only one configuration function.
X (y a) {value = a.val;} OK, you can do this, you can implement this example on your compiler, and you can solve all problems (there will be a problem in VC because VC is in temporary objects This is not good enough for standard C . When you use a temporary object, you can also compile the pass). Here I give an actual example of Auto_PTR, I think you should be able to understand it :)
Template struct auto_ptr_ref {y * yp; auto_ptr_ref (y * rhs): YP (rhs) {}}; // pay attention to this conversion layer
Template class auto_ptr1 {private: t * ap; public: typedef t element_type;
Explicit auto_ptr1 (t * ptr = 0) throw (): AP (PTR) {}
Auto_PTR1 (Auto_PTR1 & RHS) throw (): AP (rhs.release ()) {}
Template auto_ptr1 (auto_ptr1 & rhs) throw (): AP (rhs.release ()) {}
Auto_PTR1 & OPERATOR = (Auto_Ptr1 & RHS) throw () {reset (rhs.release ()); return * this;}
Template auto_ptr1 & operator = (auto_ptr1 & r Hs) throw () {reset (rhs.release ()); return * this;}
~ Auto_PTR1 () throw () {delete ap;}
T * GET () const throw () {return ap;}
T & Operator * () const throw () {return * ap;}
T * Operator -> () const throw () {return ap;} t * release () throw () {t * tmp (AP); AP = 0; Return TMP;}
Void Reset (T * PTR = 0) throw () {if (ap! = ptr) {delete ap; AP = PTR;}} auto_ptr1 (auto_ptr_ref rhs) throw (): AP (rhs.yp) {}
Auto_PTR1 & OPERATOR = (Auto_PTR_REF RHS) throw () {reset (rhs.yp); return * this;}
Template Operator auto_ptr_ref () throw () {return auto_ptr_ref (Release ());}
Template operator auto_ptr1 () throw () {return auto_ptr1 ());
Ok, I will say this today. If you have any questions, you can ask