More Effective C ++ Terms 28 (in)

zhaozj2021-02-08  280

Terms 28: Smart (SMART) pointer (medium)

Whether the test dexterity pointer is NULL

The function we discussed so far allowed us to build, release, copy, assign value, and Dereference delegated pointers. But there is a thing we can't do is "Discovering the Deliby Pointer to NULL":

Smartptr PTN;

...

IF (PTN == 0) ... // Error!

IF (PTN) ... // Error!

IF (! ptn) ... // error!

This is a serious limit.

Join an IsNull member function in a dexterity normally is a very easy thing, but still does not solve the problem of behavior of the NULL and the Dumb Pointer discomfort. Another method is to provide implicit type conversion operators that allow compilation of the above tests. The type conversion generally applied to this purpose is void *:

Template

Class smartptr {

PUBLIC:

...

Operator void * (); // If the delegation pointer is NULL,

... // Return 0, otherwise return

}; // Non 0.

Smartptr PTN;

...

IF (PTN == 0) ... // is now correct

IF (PTN) ... // is also correct

IF (! ptn) ... // correct

This is the same as the type conversion provided in the iostream class, so you can write code like this:

IFStream InputFile ("DataFile.dat");

IF (InputFile) ... // Test if INPUTFILE has been

// Successfully opened.

Like all type conversion functions, it has a shortcomings in some cases, although most programmers want it to call fails, but functions can be successfully called (see Terms 5). In particular, it allows a smart pointer to compare between completely different types:

Smartptr pa;

Smartptr PO;

...

IF (PA == PO) ... // This can be successfully compiled!

Even if there is no Operator = function between SmartPtr and SmartPtr , it is also possible to compile, because the smart pointer is implicitly converted to a VOID * pointer, there is a built-in comparison function for the internal pointer type. This behavior characteristic of this implicit type conversion is very dangerous. (Take a look at the terms 5, you must read it back and read, do you understand.)

There are also some changes in void * type conversion. Some designers use the type conversion of const void *, and some ways to convert to BOOL. These variations have not eliminated the problem of comparison of mixed types.

There is a two-whole policy that provides a reasonable form of test null value, while the possibility of comparing different types of delegated pointers is minimized. This is to overrunate Operator in the delegation pointer! And when and only when the delegation pointer is an empty pointer, Operator! Returns true:

Template Class Smartptr {

PUBLIC:

...

Bool Operator! () const; // When and only a dexterous pointer is

... // Null value, return true.

}

The client program is as follows:

Smartptr PTN;

...

IF (! PTN) {// correct

... // PTN is null

}

Else {

... // PTN is not null value

}

But this is not correct:

IF (PTN == 0) ... // Still error

IF (PTN) ... // is also wrong

There is only different types of different types in this case:

Smartptr pa;

Smartptr PO;

...

IF (! pa ==! po) ... // can compile

Fortunately, the programmer will not write code like this. Interestingly, in addition to providing Void * implicit type conversion, the iostream library has Operator! Functions, but the two functions are used to test the state of flow. (See the C class library standard (see Effective C Terms 49 and Terms 35), Void * implicit type conversion has been replaced by the BOOL type conversion, and the Operator Bool always returns to Operator! The opposite value.)

Transform the dexterity pointer into DUMB pointer

Sometimes you have to add a delegated pointer in a program or have already used the DUMB pointer. For example, your distributed database system is not a distributed, so there may be some old-fashioned library functions without using a smart pointer:

Class Tuple {...}; //

Void Normalize (tuple * pt); // put * Pt into

// paradigm; pay attention

/ / Is the DUMB pointer

Consider what happens if you try to use the smart pointer of Tuple to call Normalize:

DBPTR PT;

...

Normalize (PT); // Error!

This call cannot be compiled because DBPTR cannot be converted to Tuple *. You can do this, so that the function is running normally:

Normalize (& * pt); // cumbersome, but legal

But I think you will hate this way of calling.

Increase the implicit type conversion operator pointing to the DUMB pointer pointing to T in the delegated pointer template, allowing the above function call to successfully run:

Template //

Class dbptr {

PUBLIC:

...

Operator t * () {return pointee;}

...

}

DBPTR PT;

...

Normalize (PT); // can run

And this function also eliminates the problem of test null value:

IF (Pt == 0) ... // correct, turn the PT to // tuple *

IF (pt) ... //

IF (! pt) ... //> Reprise

However, it also has the shortcomings of type conversion functions (almost always like this, see clause 5). It enables the client to easily access the DUMB pointer directly, bypass the "Pointer-Like" object provided by the "Pointer-like" object:

Void Processtuple (DBPTR & Pt)

{

Tuple * RawTuplePtr = Pt; // Transist DBPTR

// tuple *

Modify TUPLE using RAW TUPLEPTR;

}

Typically, the "smart" behavior feature provided by the smart pointer is the main components in the design, so allowing clients to use DUMB pointers to lead to catastrophic consequences. For example, if DBPTR implements a function of reference count in Terms 29, allowing clients to operate directly to DUMB pointers, which is likely to destroy the "reference count" data structure, resulting in reference count errors.

Even even if you provide an implicit conversion operator from a smart pointer to the DUMB pointer, the smart pointer cannot really be interchanged with the DUMB pointer. Because the conversion from the smart pointer to the DUMB pointer is "User-Defined Type Conversion", the number of transitions in the same time compiler cannot exceed once. For example, it is assumed that there is a class that represents all customers who can access a group:

Class TupleAccessors {

PUBLIC:

TupleAccessors (const tuple * pt); // Pt Identifies the

... // Tuple Whose Accessors

}; // We Care About

Typically, TupleAccessors' single-parameter constructor can also be used as a type conversion operator from Tuple * to TupleAccessors (see Terms 5). Now consider the functions used to merge the information within the two TupleAccessors objects:

TupleAccessors Merge (Const Tupleaccessors & TA1,

Const TupleAccessors & TA2);

Because a tuple * can be implicitly converted to TupleAccessors, use two DUMB TUPLE * to call the MERGE function, you can run normal:

TUPLE * PT1, * PT2;

...

MERGE (PT1, PT2); / / correct, two pointers are converted to

// TupleAccessors Objects

If you call with a dexterin DBPTR , compile will fail:

DBPTR PT1, PT2;

...

MERGE (PT1, PT2); // Error! Can't put PT1 and

// PT2 conversion TUPLEACCESSORS object

Because converted from DBPTR to TupleAccessors To call two user-defined type conversion (once from dbptr to tuple *, once from tuple * to tupleAccessors), the compiler does not perform this conversion sequence.

The smart pointer class that provides implicit type conversion to the DUMB pointer has also exposed a very harmful bug. Consider this code: dbptr pt = new tuple;

...

delete pt;

This code should not be compiled, PT is not a pointer, it is an object, you can't delete an object. Only the pointer can be deleted, right?

Of course it is right. But recall the Terms 5: The compiler uses implicit type conversions to make the function call successfully, then recall the Terms 8: Use the delete to call the destructor and Operator Delete, both are functions. The compiler wants to successfully call the two functions in the Delete statement, and the PT implicitly converts the PT to TUPLE * and then deletes it. This will inevitably destroy your program.

If the PT has the object it pointing, the object will be deleted twice, once when Delete is called, the destructor of the PT is called. If PT does not have objects, but others have, the owner can delete PT, but if the PT pointing to the owner of the object does not delete the PT person, the owner with delete will also delete the object again. Whether it is the situation described in the former or the latter case, an object is deleted twice, which will result in an unpredictable consequence.

This bug is extremely harmful, because all the idea hidden behind the smart pointer is to let them look like the DUMB pointer in appearance or in use. The more you get close to this kind of thought, the more you, the more you forget that is using the dexterity pointer. If they have forgotten that they are using the dexteps, they will definitely call Delete after calling New to prevent resource leakage, who can blame them to do this?

The bottom line is simple: unless there is a very convincing reason to do this, do not provide the implicit type conversion operator that converts to the DUMB pointer.

The type conversion of delegated pointers and inherits

Suppose we have a public inheritance hierarchy to model the product of the music store:

Class MusicProduct {

PUBLIC:

MusicProduct (const string & title);

Virtual void play () const = 0;

Virtual void displaytitle () const = 0;

...

}

Class CassetTe: Public MusicProduct {

PUBLIC:

CassetTe (const string & title);

Virtual Void Play () Const;

Virtual void displaytitle () const;

...

}

Class CD: Public MusicProduct {

PUBLIC:

CD (const string & title);

Virtual Void Play () Const;

Virtual void displaytitle () const;

...

}

Then, it is assumed that we have a function, give it a MusicProduct object, which shows the product name, and play it:

Void DisplayandPlay (const MusicProduct * PMP, int NumTIMES)

{

For (INT i = 1; i <= NumTIMES; i) {

PMP-> DisplayTITLE ();

PMP-> Play ();

}

}

This function can use this:

Cassette * funmusic = new cassette ("alapalooza");

CD * Nightmaremusic = New CD ("Disco Hits of The 70S"); DisplayandPlay (Funmusic, 10);

DisplayandPlay (Nightmaremusic, 0);

This doesn't have a surprising thing, but when we replace the DUMB pointer with a smart pointer:

Void Displayandplay (Const Smartptr "MusicProduct> & PMP,

INT NUMTIMES);

SmartPtr funmusic (New Cassette ("Alapalooza");

Smartptr Nightmaremusic (New CD ("Disco Hits of The 70s);

DisplayandPlay (Funmusic, 10); // Error!

DisplayandPlay (Nightmaremusic, 0); // Error!

If the dexterity pointer is so smart, why can't you compile these code?

Can't compile because SmartPtr or SmartPtr is converted into SmartPtr . From the viewpoint of the compiler, there is no relationship between these classes. Why do the compiler think so? After all, SmartPtr or smartptr is not inherited from SmartPtr , there is no inheritance between these classes, and we cannot ask the compiler to convert an object into another type of object.

Fortunately, there is a way to avoid this restriction, the core idea of ​​this method (not actual operation) is very simple: each of which can be implicitly converted to provide an implicit type conversion operator (see Terms 5). For example, within the Music class level, you can join the SmartPtr function within Cassette and CDs:

Class Smartptr {

PUBLIC:

Operator smartptr ()

{Return Smartptr (POINTEE);

...

Private:

Cassette * Pointee;

}

Class Smartptr {

PUBLIC:

Operator smartptr ()

{Return Smartptr (POINTEE);

...

Private:

Cd * Pointee;

}

This method has two shortcomings. First, you must be a Specialize SmartPTR class, so you join the implicit type conversion operator to destroy the versatility of the template. Second, you may have to add a number of types of converters because the objects you point to can be located in a deep position in the inheritance level, and you must provide a type of converter to each base class directly or indirectly inherited. (If you want you to overcome this shortcomings, the method is to provide an implicit type converter for the transition to the direct base class, then you think so again? Because the compiler calls the user-defined type conversion function at the same time. The number of times cannot be more than once, they cannot convert the smart pointers pointing to T to the indirect base class pointing to T, unless you can complete it as soon as one step.)

If you enable the compiler to write all type conversion functions for you, save a lot of time. Thanks to the nearest language extension, let you do it, this extension can declare (non-virtual) member function template (usually called member template), you can use it to generate a dexterous pointer type conversion function, as follows: Template // Template class, pointing T

Class smartptr {// delegated pointer

PUBLIC:

SmartPtr (T *RPTR = 0);

T * Operator -> () const;

T & Operator * () const;

Template // template member function

Operator smartptr () // In order to implement implicit type conversion.

{

Return Smartptr (POINTEE);

}

...

}

Now please pay attention, this is not a magic - but it is also very close to the magic. Its principle is as follows. (If the content below makes you feel that the lengthy and make you refill, please don't be disappointed, I will give an example. I promise that after you read the example, you can understand this content more deeper.) Assume the compiler A smart pointer pointing to the T object, it wants to convert this object into a dexterous pointer to the "T-based class". The compiler first checks the class definition of SmartPtr to see if there is a type of converter that must be documented, but it has no declaration. (This doesn't mean: There is no declared type converter on the template) The compiler then checks if there is a member function template, which can be instantiated to perform the type conversion it expects. It found a template (with form type parameter newtype), so it binds NewType to T. The template is instantiated. At this time, only one question is whether the instantiated member function code can be compiled. Transfer (DUMB) pointer Pointee to the constructor of the smart pointer to "t", this statement is legal, and it is inevitable that it turns into a pointer to its base class (public or protected) object. Therefore The type conversion operator can be compiled, and the intersection of the smart pointer pointing to T is successfully converted to the dexterity pointer to the "base class".

Hold an example will help. Let us return to the inheritance of CDS, Cassettes, and Music products. We have previously known that the following code cannot be compiled because the compiler cannot convert the smart pointer to the CD to the smart pointer to the MUSIC product:

Void Displayandplay (Const Smartptr "MusicProduct> & PMP,

Inthnmany;

SmartPtr funmusic (New Cassette ("Alapalooza");

Smartptr Nightmaremusic (New CD ("Disco Hits of The 70s);

DisplayandPlay (Funmusic, 10); // is previously an error

DisplayandPlay (Nightmaremusic, 0); // is previously an error

After modifying the smart pointer, this code can be successfully run after the member function template of the implicit type conversion operator. Take the following calls to see why you can run success: DisplayandPlay (Funmusic, 10);

The type of Funmusic object is smartptr . The parameters expected by the function displayandplay are smartptr . The compiler detects the type mismatch and finds the way funmusic into a SmartPtr object. It looks for a single parameter constructor with SmartPtr class (see Terms 5), but not found. They then look for member function templates that can be instantiated to generate such functions. They found templates in SmartPtr , bind NewType to MusicProduct, generate a must function. Instantiate function, generate such code:

Smartptr :: operator smartptr ()

{

Return Smartptr (POINTEE);

}

Can you compile this line of code? In fact, this code is to use pointee as a parameter to call the constructor's constructor, so the real problem is to construct a smartptr object with a CassetTe * pointer, now our conversion between DUMB pointer types It is already very familiar, and Cassette * can be passed to a place where the MusicProduct * pointer is required. Therefore, the SmartPtr constructor can successfully call, the same SMARTPTR to SmartPtr can also be successful. Great, realize the type conversion between the smart pointers, what is more simple than this?

And, what is more powerful than this function? Do not mislead this example, and think that this method can only be used to convert the pointer upwards in the inheritance level. This method can be successfully used for any legal pointer type conversion. If you have DUMB pointer T1 * and another DUMB pointer T2 *, when you can implicate T1 * to T2 *, you can implicitly convert to T1's smart pointer type to point to T2's smart pointer type.

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

New Post(0)