More Effective C ++ Terms 28 (on)

zhaozj2021-02-08  285

Terms 28: Smart (SMART) pointer (on)

The delegated pointer is an appearance and behavior being designed to be similar to the internal pointer, but it provides more features. They have many applications, including resource management (see Terms 9, 10, 25 and 31) and Automation of Duplicate Code Task (see Terms 17 and 29)

When you use a dexterity pointer to replace C 's built-in pointer (that is, Dumb Pointer), you can control the behavior of the pointers below:

Construction and destructive. You can decide what to do when you build a dexterity pointer. Usually give the delegated pointer default value 0 to avoid a headache uninitial pointer. When the last delegated pointer pointing to an object is released, some smart pointers are responsible for deleting the objects they point to. This is helpful for preventing resource leakage.

Copy and assignment. You can control the assignment of copying skills or design dexterity pointers. For some types of delegation pointers, the desired behavior is automatically copied to the objects you point to, or assigning assignments to these objects, that is, perform DEEP COPY. For some other dexterity pointers, only copy the pointer itself or the pointer is assigned. There is also a part of the type of dexterity pointer to not allow these operations. No matter how you think it should be, the dexterity pointer is always your control.

Dereferencing (contents of something referred to in the pointer). What happens when the client references the object refers to the smart pointer? You can decide yourself. For example, you can use the dexterity pointer to implement the lazy fetching method mentioned in Terms 17.

The delegated pointer is generated from the template because it is necessary to be similar to the internal pointer, must be a strongly type; template parameter determines the type of object to the object. Most delegated pointer templates look like this:

Template // Derivative pointer icon template

Class smartptr {

PUBLIC:

Smartptr (T *RPTR = 0); // Establish a dexterous pointer

/ / Point to Dumb Pointer

// object. Not initialized pointer

/ / The default is 0 (NULL)

Smartptr (const smallptr & rhs); // Copy a dexterity pointer

~ Smartptr (); // Release the intelligence pointer

// Make an Assignment to A Smart Ptr

Smartptr & Operator = (Const Smartptr & RHS);

T * Operator -> () const; // dereference a dexterity pointer

// member to access the object of the object

T & operator * () const; // derecrence delegated pointer

Private:

T * pointee; // The object refers to the pointer

}

Copy constructor and assignment operators are presented here. For smart pointer, copy and assignment cannot be allowed, and they should be declared as Private (see Effective C Terms 27). The two Dereference operators are declared as constings because the pointer cannot be modified when a pointer is a pointer (although the object refers to the pointer) is modified. Finally, each dexterin pointer to the T object contains a Dumb Pointer pointing to T. This Dumb Pointer is pointing to the true object that the dexterity pointer points to.

Before entering the details of the dexterity pointer, you should study how the client uses the dexterity pointer. Consider, there is a distributed system (ie some of the objects on the local, some in remote). Compared to accessing the remote object, accessing local objects is usually always simple and fast, because remote access requires remote procedure call (RPC), or some other ways to connect remote computers. For clients with program code, different methods are used to handle local objects and remote objects, respectively, is an annoying thing. Allows all objects that are more convenient in one place. The smart pointer allows the library to achieve such a dream.

Template // points to a distributed DB (database)

Class dbptr {// Dimensions of objects

PUBLIC: //

DBPTR (T *RPTR = 0); // Establish a smart pointer, pointing

// by a local DUMB POINTER

// give the DB object

DBPTR (DatabaseID ID); // Establishing a dexterous pointer,

// Point to a DB object,

/ / Have a unique DB identifier

... // Other Ligrated Pointer Functions

}; //

Class Tuple {// Database Yuan Group

PUBLIC:

...

Void Displayeditdialog (); // Displays a graphics dialog,

// Allow the user to edit the group.

// user to edit the tuple

Bool isvalid () const; // Return * this is passed

}; // legal verification

// This class template is used to log registration when modifying the T object.

// Refer to the following description:

Template

Class LogenTry {

PUBLIC:

LOGENTRY (Const T & ObjectTobemodified);

~ Logentry ();

}

Void EditTuple (DBPTR & Pt)

{

LOGENTRY Entry (* pt); / / Register the log for this editing operation

/ / Refer to the following description

// Repeat the Edit dialog until a legal value is provided.

Do {

Pt-> Displayeditdialog ();

} while (pt-> isvalid () == false);

}

The tuples that are edited in EditTuple can be physically located in the local location, but the programmer writes EditTuple does not have to care for these things. The smart pointer is hidden in these aspects of the system. Programmers only need to care for the tuples accessed through the object, without care, how to declare them, their behavior is like an internal pointer.

Note the usage of the LogenTry object in EditTuple. A more traditional design is to start logging before calling DisplayeditDialog, and end logging after calling. The method used here is to let LoGENTRY's constructor start logging, and the destructor ends logging. As explained in Terms 9, when facing an abnormality, let the object you start and end the log record more robust than the displayed function can make the program. And create a LoGENTRY object is easier than the start recording and ending record functions each time. As you can see, use the dexterity pointer and use Dump Pointer no big difference. This indicates that the package is very effective. The client of the dexterity pointer can use a dexterous pointer like using Dumb Pointer. As we will see, sometimes this alternative will transparent.

Construction, assignment, and sectations of dexterity pointers

The descent of the delegated pointer is usually very simple: find the object pointing to (generally given the parameters of the smart pointer constructor), let the internal member Dumbut of the smart pointer points to it. If an object is not found, set the internal pointer to 0 or send an error signal (which can be throwing an exception).

The integrity of the constructor, the assignment operator function, and the destructive function of the designer are somewhat complicated due to the problem of ownership. If a delegated pointer has the object it pointing, it must be deleted when it is released when it is released. This assumes that the object that the dexterity pointer points to the dynamically assigned. This assumption is common in a smart pointer (see Terms 27) for determining this hypothesis is true.

Look at the Auto_PTR template in the standard C class library. This is explained in Terms 9, an Auto_PTR object is a smart pointer to the heap object until Auto_Ptr is released. What happens when the destructor of Auto_PTR deletes the object it points to? The actual auto_ptr template is as follows:

Template

Class auto_ptr {

PUBLIC:

Auto_PTR (T * PTR = 0): Pointee (PTR) {}

~ Auto_PTR () {Delete Pointee;

...

Private:

T * pointee;

}

If Auto_PTR has an object, it can run normally. But what happens when auto_ptr is copied or assigned?

Auto_Ptr PTN1 (New Treenode);

Auto_PTR PTN2 = PTN1; // Call the copy constructor

What happens?

Auto_Ptr PTN3;

PTN3 = PTN2; // Call OPERATOR =;

What happens?

If we only copy the internal Dumb Pointer, it will cause two auto_ptr to point to an identical object. This is a disaster, because each Auto_PTR will delete the objects they refer to when released QUTO_PTR. This means that an object will be deleted twice. The result of this twice deletion will be unpredictable (usually catastrophic).

Another method is to create a new copy of the object referred to by calling New. This ensures that there will be no many auto_ptrs to the same object, but the new object will cause unacceptable performance loss. And we don't know what type of object to establish, because the auto_ptr object does not need to point to the type T, which can also point to the derived type object of T. Virtual constructor (see Terms 25) may help us solve this problem, but it seems that you cannot use them in the general class such as Auto_Ptr.

If the QUTO_PTR prohibits copy and assignment, it is possible to eliminate this problem, but when "When Auto_Ptr is copied and assigned, the object ownership is passed" is a more flexible solution: Template

Class auto_ptr {

PUBLIC:

...

Auto_PTR (Auto_Ptr & RHS); // Copy Construction Function

Auto_Ptr & / / Assignment

Operator = (auto_ptr & rhs); // operator

...

}

Template

Auto_PTR :: auto_ptr (auto_ptr & rhs)

{

Pointee = rhs.pointee; // Put * Pointee ownership

// Pass to * this

RHS.POINTEE = 0; // rhs no longer owns

} // anything

Template

Auto_PTR & Auto_Ptr :: Operator = (Auto_Ptr & RHS)

{

IF (this == & rhs) // If this object is self assigned

Return * this; // Don't do anything

Delete Pointee; // Delete the object now owned

Pointee = rhs.pointee; // Put * Pointee ownership

RHS.POINTEE = 0; // Pass from RHS to * this

RETURN * THIS;

}

Note that the assignment operator must delete the object you have before accepting the ownership of the new object. If not do it, the original owners will never be deleted. Remember, in addition to the auto_ptr object, no one has an object pointing to Auto_Ptr.

Since the copy constructor of Auto_PTR is called, the ownership of the object is passed, so the auto_ptr object is passed through the pass value is a very bad way. because:

// This function is usually caused by disaster.

Void PrintTreenode (Ostream & S, Auto_Ptr P)

{S << * p;

int main ()

{

Auto_Ptr PTN (New Treenode);

...

PRINTTREENODE (COUT, PTN); // Transfer Auto_Ptr by passing

...

}

When PRINTTREENODE's parameter P is initialized (call AUTO_PTR copy constructor), PTN points to the ownership of the object to be passed to P. When the PRINTTREENODE ends the execution, P leaves the scope, and its destructor deletes the object it points to (it is the object that PTR points to "). However, PTR no longer points to any object (its Dumb Pointer is NULL), so that any attempts to use after call PrintTreenode will generate unfained behavior. The auto_ptr can only be passed through the pass value when you really want to pass the ownership of the object to a temporary function parameter. This situation is rare. This is not to say that you can't communicate auto_ptr as a parameter, which means that the method of passing cannot be used. This is like this: Pass-by-reference-to-const method is like this:

// This function is more intuitive

Void PrintTreenode (Ostream & S,

Const auto_ptr & P)

{S << * p;

In the function, P is a reference, not an object, so it does not call the copy constructor initialization P. When the PTN is passed to this PRINTTREENODE, it also retains the ownership of the object, and the PRINTTREENODE can be safely used in the future. This allows the risk to avoid the risk of passing values ​​via const reference. ("Reference Pass" replaces other reasons for "passing values" See Effective C Terms 22).

In copy and assignment, pass the ownership of the object from a dexterity pointer to another, this kind of thought is interesting, and you may have noticed that the copy constructor and the unusual declaration method of assignment operators are also very interesting. These functions will have a const parameter, but there is no above these functions. These parameters are actually modified in the copy and assignment of these codes. That is, if the auto_ptr object is copied or as an assignment data source, the auto_ptr object is modified!

Yes, it is like this. C is so flexible to make you do this, it is so good. If the language requires that the copy constructor and assignment operator must have a const parameter, you must remove the const attribute of the parameter (see Effective C Terms 21) or use other methods to achieve ownership transfer. Accurately: This object is modified when copying an object or this object as an assignment. This may be somewhat not intuitive, but it is simple, straightforward, and it is also accurate in this case.

If you find that these auto_ptr member functions are very interesting, you may want to see the complete work. There are (only the original book page number) on page 291 to 294, where you can also see the AUTO_PTR template in the standard C library has a more flexible copy constructor and assignment operator described herein. In a standard C library, these functions are members of the member function template, not just a member function. (The member function template will be described later. You can also read the Effective C Terms 25).

The destructive function of the dexterity pointer is usually this:

Template

Smartptr :: ~ smartptr ()

{

IF (* this owns * pointee) {

DELETE POINTEE;

}

}

There is no need to test before deleting, for example, when an auto_ptr always has object it points to. At other times, the test will be more complicated. A deliberate pointer using a reference count (see Terms 29) The dexterity pointer must be adjusted to adjust the reference count before determining whether the reference is removed. Of course, there are some smart pointer DUMB POINTER, and when they are deleted, there is no impact on the object. Practical dereference operator

Let us turn your attention to the core part of the smart pointer, the Operator * and Operator-> functions. The former returns the object referred to. In theory, this is very simple:

Template

T & SmartPtr :: Operator * () Const

{

Perform "Smart Pointer" Processing;

Return * Pointee;

}

First, no matter what function, you must initialize the pointer or make Pointee legal. For example, if you use Lazy Fetch (see Terms 17), the function must establish a new object for Pointee. Once Pointee is legal, the Operator * function returns a reference to its object.

Note that the return type is a reference. If the object is returned, although the compiler is allowed to do so, this will also lead to catastrophic consequences. It is necessary to keep in mind: Pointee does not need to point to T type objects; it can also point to the derived class object of T. If the Operator * function returns in this case, the T type object is not a reference to the derived class object, and your function is actually returned is an error type of object! (This is a sliant problem, see Effective C Terms 22 and Book Terms 13). Call the virtual function on the returned object, which does not trigger a function that matches the dynamic type of the object. In fact, it is not possible to support virtual functions, like this pointer is also useless. Returns a reference can also have higher efficiency (no need to construct a temporary object, see Terms 19). It is of course a good thing to take care of the correct and efficiency.

If you are an acute child, you may think if some people call the Operator * on the Null delegation pointer, that is, the Dumb Pointer of the delegation pointer is NULL. Relax. Just do anything. Dereference is undefined results in an empty pointer, so this is not a "error" behavior. Do you want to exclude an exception? Yes, throw it. Want to call an Abort function (may be called when you fail at failure)? Ok, call it. Do you want to traverse the memory to set each byte to your birthday and 256 modulus? Of course. Although there is nothing benefit to do this, but in the language itself, you are completely free.

The situation of operator-> is the same as Operator *, but before analyzing Operator->, let us recall the meaningful meaning of this function call. Consider the EditTuple function, use a smart pointer to the Tuple object:

Void EditTuple (DBPTR & Pt)

{

LOGENTRY Entry (* Pt);

Do {

Pt-> Displayeditdialog ();

} while (pt-> isvalid () == false);

}

Statement

Pt-> Displayeditdialog ();

The compiler is interpreted as: (pt.operator -> ()) -> Displayeditdialog ();

This means no matter what Operator-> returns, it must use the Member-Selection Operator (->). So Operator-> can only return two things: a Dumb Pointer or another delegated pointer to an object. In most cases, you want to return a normal Dumb Pointer. In this case, you do this in this case, Operator->: Template

T * SmartPtr :: Operator -> () const

{

Perform "Smart Pointer" Processing;

Return pointee;

}

This is good to run. Because the function returns a pointer, the behavior is also correct by operating the virtual function through the Operator->.

For many programs, this is what you need to understand the whole thing of the mind. The reference count code of clause 29 is not more functions than here. But if you want to know more deeply, you must know more about Dumb Pointer's knowledge and dexterity pointers can or cannot be simulated. If your motto is "Most People Stop At the Z-But Not Me (most people are exciting, but I can't be like this)

", The contents described below are suitable for you.

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

New Post(0)