Effective C ++ 2e Item 14

zhaozj2021-02-11  188

Terms 14: Determine the base class with a false prevention function

Sometimes, a class wants to track it how many objects exist. A simple method is to create a static class member to count the number of objects. This member is initialized to 0, plus 1 in the constructor, and the destructive function is reduced. (The Terms M26 illustrates how this method is encapsulated to easily add it to any class, "My Article on Counting Objects provides additional improvements to this technology)

Imagine a class that represents the enemy's goal in a military app:

Class EnemyTarget () { NumTarget ()} ( numtarget);} ~ enemtyTarget ()} ~

Static size_t numberoftargets () {return numtargets;}

Virtual Bool Destroy (); // Destroy the enemyTarget Object // Return successfully

Private: static size_t NumTargets; // Object counter};

// The static member of the class is to define an external definition; / / The default is initialized to 0Size_t EnemyTarget :: NumTargets;

This class will not win a government defense contract, which is too far from the Ministry of Defense, but it is enough to meet the needs of our explanation.

The enemy's tank is a special enemy goal, so it will naturally think of abstracting it into a class that is derived from EnemyTarget in public inheritance (see Terms 35 and M33). Because not only cares about the total number of enemy goals, we must also care about the total number of enemy tanks, so like the base class, in the derived class, the same skills mentioned above:

Class Enemytank: Public EnemyTarget {public: Enemytank () { nuMtanks;}

EnemyTank (Const Enemytank & RHS): EnemyTarget (RHS) { Numtanks;}

~ Enemytank () {--NUMTANKS;

Static size_t numberoftanks () {return nuMtanks;}

Virtual bool destroy ();

PRIVATE: Static size_t nuMtanks; // Tank Object counter};

(After writing the code above the two classes, you can understand the General Solutions for this issue by the terms M26.)

Finally, assume that some of the other somewhat NEW is dynamically created a Enemytank object, and then delete it with delete:

EnemyTarget * targetptr = New Enemytank;

...

DELETE TARGETPTR;

Everything what to do this is normal: two classes cleared the operations made by the constructor in the destructor; the application is obviously not wrong, using the object generated by New, in the end, DELETE deleted. However, there is a big problem here. The behavior of the program is unpredictable - can't know what will happen.

C language standards About this problem is very clear: When deleting objects of derived classes via a base class, the base class does not have a false argument function, the result will be unsure. This means that the code generated by the compiler will do anything it like: re-format your hard drive, send your boss to send your own email, send your program source code to your opponent, no matter what . (I often happen actually, the destructor of the derived class will never be called. In this case, this means that when TargetPtr is deleted, the number of Enemytank will not change, then the number of enemy tanks is wrong. What consequences will be caused to avoid this problem with highly dependent on accurate information?) Only need to make the EnemyTarget's destructor is Virtual. The declaration of the patterned function will bring you a good behavior that you want: Object memory is released, the destructor of the Enemytank and EnemyTarget is called.

Like most of the base classes, the EnemyTarget class now contains a virtual function. The purpose of virtual functions is to let the derive class to customize their behavior (see Terms 36), so almost all base classes contain virtual functions.

If a class does not contain a virtual function, it is usually used to use it as a base class. When a class is not prepared as a base class, the destructive function is generally a bad idea. Refer to the example below, this example is based on a special discussion of the book book book by ARM ("The Annotated C Reference Manual).

// A class of classes that represent 2D points Class Point {public: Point (Short Int Xcoord, Short Int Ycoord); ~ POINT ();

PRIVATE: SHORT INT X, Y;};

If a short int accounts for 16 bits, a POINT object will just be fitted into a 32-bit register. In addition, a POINT object can be used as a 32-bit data transfer to a function written in other languages ​​such as C or Fortran. However, if the destructor of Point is virtual, the situation changes.

Implementing the virtual function requires some additional information to enable the object to determine which virtual function of the call when the object is running. For most compilers, the specific form of this additional information is a pointer called VPTR (virtual function table pointer). VPTR points to a function pointer array called VTBL (virtual function table). Each class with virtual functions comes with a VTBL. When requesting a virtual function of an object, the actually called function is determined based on the VPTR to the VTBL to find the corresponding function pointer in VTBL.

Detail of virtual function is not important (of course, if you are interested, you can read the terms M24), it is important that if the Point class contains a virtual function, its object's volume will not know unconsciously, from 2 16-bit SHORT became 2 16-bit Short plus a 32-bit VPTR! Point objects can never be placed in a 32-bit register. Moreover, the POINT object in C does not have the same structure as the other language as declared in C, because there is no VPTR in these languages. So, use other language writes to pass the Point is no longer possible, unless it is designed to design VPTR, which itself is implemented, which will cause the code to not be ported.

So the basic one is that no reason is wrong, and never declares. In fact, many people summarize: When and only when the class contains at least one virtual function, the false argument function is declared.

This is a good guideline, most of which apply. But unfortunately, when there is no virtual function in the class, it will also bring non-false argument function. For example, the clause 13 has a class template that implements the upper limit of the user's custom array. Assuming you (advice on the Terms M33) decided to write a derived class template to represent some group of arrays (ie, there is a name per array). Template // Base class template class array {// (from Terms 13) Public: array (int lowbound, int highbound); ~ array ();

PRIVATE: Vector data; size_t size; int lbound, hbound;

Template Class NamedArray: Public Array {public: NamedArray (int lowbound, int highbound, const string & name); ...

PRIVATE: STRING ARRAYNAME;

If you are in a place in an application, you will point to the NameDArray type pointer to the Array type pointer, then use Delete to remove the Array pointer, then you will immediately fall into the trap of "Uncertain Behaviors".

NamedArray * PNA = New NamedArray (10, 20, "impending doom");

Array * pa;

...

PA = PNA; // NamedArray * -> array *

...

Delete Pa; // Uncertain! In practice, PA-> ArrayName // will cause leaks, because * PA's nameDARRAY / / will never be deleted

In reality, this situation has more frequent than you imagined. Let an existing class do something, then derive a class to do the same thing, plus some special features, which is not uncommon in reality. NamedArray did not redefine any behavior of Array - it inherited all the features of Array without any modification - it just added some additional features. However, the problem of non-false argument functions still exists (there are other problems, see M33)

Finally, it is worth noting that it is convenient to declare that the pure puzzle is very convenient in some categories. The pure virtual function will generate an abstract class - a class that cannot be instantiated (ie, this type of object cannot be created). Sometimes, you want a class to become an abstract class, but just there is no pure virtual function. How to do? Because the abstraction class is ready to be used, the base class must have a false preframe function. The pure virtual function generates an abstraction class, so the method is very simple: I want to be an abstract class to declare a pure epitope Structure function.

Here is an example:

Class AWOV {// AWOV = "Abstract w /// Virtuals" public: Virtual ~ AWOV () = 0; // Declare a pure false prevention function};

This class has a pure virtual function, so it is abstract, and it has a false prevention function, so it will not generate a destructive function problem. But there is still one thing: you must provide a definition of a pure false array function: awov :: ~ awov ()} // Definition of the pure false arrangement function

This definition is required, because the mode of forming function works is that the destructive function of the most underlying derived class is first called, and then the destructor of each base class is called. That is to say, even if it is an abstract class, the compiler also produces a call to ~ AWOV, so to ensure that it provides a function body. If you don't do this, the linker will detect, and finally I have to go back to add it.

You can do anything in a function, but just as the above example, nothing is not common. If this is the case, it naturally thinks that the destructor declares as an inline function, thereby avoiding the overhead of the call to an empty function. This is a good way, but there is one thing to be clear.

Because the designer function is the virtual, its address must enter the VTBL of the class (see Terms M24). However, the inline function is not existing as a separate function (this is the meaning of "inline"), so they must be obtained with special methods. Terms 33 have a comprehensive introduction to this, and its basic point is that if the false argument function is inline, it will avoid calling the expenses generated, but the compiler is still inevitable to generate a copy of this function.

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

New Post(0)