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
...
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
...
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
Smartptr
...
IF (PA == PO) ... // This can be successfully compiled!
Even if there is no Operator = function between SmartPtr
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
PUBLIC:
...
Bool Operator! () const; // When and only a dexterous pointer is
... // Null value, return true.
}
The client program is as follows:
Smartptr
...
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
Smartptr
...
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
...
Normalize (PT); // Error!
This call cannot be compiled because DBPTR
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
...
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
{
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
DBPTR
...
MERGE (PT1, PT2); // Error! Can't put PT1 and
// PT2 conversion TUPLEACCESSORS object
Because converted from DBPTR
The smart pointer class that provides implicit type conversion to the DUMB pointer has also exposed a very harmful bug. Consider this code: dbptr
...
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
Smartptr
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
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
Class Smartptr
PUBLIC:
Operator smartptr
{Return Smartptr
...
Private:
Cassette * Pointee;
}
Class Smartptr
PUBLIC:
Operator smartptr
{Return Smartptr
...
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
Class smartptr {// delegated pointer
PUBLIC:
SmartPtr (T *RPTR = 0);
T * Operator -> () const;
T & Operator * () const;
Template
Operator smartptr
{
Return Smartptr
}
...
}
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
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
Smartptr
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
Smartptr
{
Return Smartptr
}
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
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.