Class 14 The sectoral function of the base class must be defined as a virtual function (from effective C ++)

zhaozj2021-02-08  226

Sometimes a class needs to know how many objects currently have, the most direct way to achieve this destination is to define a static member variable for statistical objects. This variable is initialized to 0, and add 1 when the class constructor is called, and when the destructor is called 1.

Suppose you are writing a military program, one of which means the class definition of the enemy target is as follows:

Class enemytarget {

PUBLIC:

EnemyTarget () { NumTargets;}

EnemyTarget (Const EnemyTarget &) { NumTargets;}

~ EnemyTarget () {--NUMTARGETS;

Static size_t numberoftargets ()

{Return NumTargets;

Virtual Bool Destroy (); // Returns true when destroying the enemy's goal

Private:

Static size_t NumTargets; // Object Quantity

}

// The class static member variable must be defined outside the class;

/ / The default is initialized to 0

SIZE_T EnemyTarget :: NumTargets;

Of course, this class is far from the requirements of the Ministry of Defense, so it is impossible to win government defense contracts for you, but it is enough for us to explain the problem.

Assume that there is a special enemy goal in your simulation is an enemy tank, and the enemy tank is inherited from the EnemyTarget. Because you not only know the number of all enemy targets, but also interested in the number of enemy tanks, you use the same skills as the base class in derived class:

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 number

}

Now, after adding two different class code, you have understood the regular solutions of such issues introduced by the Terms M26 (Terms M26 to limit a class of objects), and may have already contained A step.

Finally, assume that in a place in the program, you have created an Enemytank object using the new dynamic, then remove it with Delete:

EnemyTarget * targetptr = New Enemytank;

...

DELETE TARGETPTR;

So far, it seems that everything is normal. Not only the two types of destructor do "revoke" operations consistent with their constructor, but also have no errors in your program - because you will use the object created by New. Delete. However, here is hidden with a very annoying question: the behavior of the program is undefined - you don't know what maybe.

At this point, the C language standard narrative is unusual: When you try to use the base class's pointer to delete the derived class object, if the base class does not define the destructor as a virtual function (like the EnemyTarget), then The result is undefined. This means that the compiler can generate code as you want to generate code, format your disk, send your boss, send the source code to your competitors, and do something. (The most likely happening when running procedures is the sect of the sectic language. In this example, this means that the number of Enemytank has not changed when TargetPtr is deleted, of course, the number of enemy tanks you are counting It is the wrong, the soldiers who depend on accurate battlefield information are miserable!) In order to avoid this problem, you can only define the destructor of the EnemyTarget as virtual. Defining the parsing function as a virtual will ensure that the behavior of this class is a well-defined, allowing it to act in your will: whether it is Enemytank or the EnemyTarget, when the memory is stored, the memory is released, The destructor will be called correctly.

Here, the EnemyTarget class has a virtual function, which is a general way to define the base class. After all, the purpose of the virtual function is to allow the behavior of the function to be re-customized in the derived class, and almost all base classes have virtual functions.

If a class does not define any virtual functions, it usually means that it is not ready for the base class. When a class is not prepared to use as a base class, the definition virtual parses function is typically an idea. Please see an example, this example comes from ARM (The Annotated C Reference Manual, Margaret Ellis and Bjarne Stroustrup ADDISON-WESLE, 1990 - Translation)

/ / Indicate the 2D point

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 is just a 32-bit register. In addition, a POINT object can be transmitted to a function written in other languages ​​(such as C or FortRan et al.) As a 32-bit quantity. However, if the destructor of the POIN is changed to the virtual, the situation is different.

Implementing the virtual function requires some additional information to bring this object to determine the virtual function that should be called at runtime. In most compilation, this additional information exists in a form called VPTR (Virtual Table Point) pointer, VPTR pointing to a function pointer called VTBL (Virtual Table), each with a virtual function. VTBL. When a virtual function of an object is called, use the VPTR to the VPTB to query the appropriate function pointer in VTBL to determine which actual function is called.

How to implement the details of the virtual function is not important, it is important if there is a virtual function if there is a virtual function in the Point class, and the actual size of this type of object will be doubled from two 16-bit shorts into two 16-bit Short plus one 32 Bit VPTR! The POINT object cannot just store it in the 32-bit register. In addition, similar structures that Point objects declared in C are not one thing, because there is no VPTR in the structure of other language declarations. This is not possible to pass the POINT object between the C function and other languages, unless you explicitly add VPTR, and this is to take into account the implementation details, it is of course not portable.

Always declare the designer as virtuality as the virtual is not as bad as the virtual. In fact, many people have drawn this rule: When and only when a class contains at least one virtual function, the virtual destructuring function of the class is defined. This is a good rule, you can work in most cases, but unfortunately, even if there is no virtual function, it is likely to have problems due to the designer of the designer. For example, Terms 13 (Terms 13: Initialization Member Variables) Design a class template for implementing user-defined arrays, suppose you want to write a template for its derived classes, The class can represent the named array, that is, each array of derived class instantiation has its own name:

Template // Base Template

Class Array {// (From Terms 13)

PUBLIC:

Array (int lowbound, int highbound);

~ Array ();

Private:

Vector DATA;

SIZE_T SIZE;

INT LBOUND;

}

Template

Class namedArray: Public Array {

PUBLIC:

NamedArray (int lowbound, int highbound, const string& name);

...

Private:

String arrayname;

}

If you will point to NamedArray's pointer to a pointer to the Array, you will use the Delete action to the Array pointer, you will immediately fall into the situation where the procedure is not defined:

NamedArray * PNA =

NEW NamedArray (10, 20, "impending doom");

Array * pa;

...

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

...

Delete Pa; // Unexpected! In fact, PA-> ArrayName's memory

// is often leaked, because * PA points to NamedArray roots

// This is not released

This kind of situation is more frequent than what you imagine, because people want an existing class (such as Array) and its school (NamedAday), this situation is a lot of situations. In the above example, NameDarray did not redefine any behavior of Array, which inherited the ARRAY function but did not change them, but added additional functions. However, the problems caused by the designer of the destructive function still exist.

Finally, it is worth mentioning that in some classes, the pure virtual function is very good. We know that classes define pure virtual functions are abstract classes - categories that cannot be instantiated (that is, you can't create this type of object). However, sometimes you want your class to be an abstract class, but it happens that there is no function is a pure virtual function. What should you do? Okay! Because this abstraction must have a base class, the base class should have a virtual destructor, and the definition of pure virtual functions produces an abstract class, so the answer is very simple: if you want to define a class as an abstract class, Simply define a pure virtual destructor for this class.

Please see this example:

Class Awov {// awov = "Abstract W / o Virtuals" public: PUBLIC:

Virtual ~ awov () = 0; // Declare a pure virtual destructor

}

This class has a pure virtual function, so it is an abstract class; and this virtual function is its destructor, so you can guarantee that you don't have to worry about the destructor. However, the above is just one, you must also provide definitions for the pure virtual destructor:

AWOV :: ~ AWOV () {} // Definition of Pure Virtual Destructor

This definition is necessary, because the order of operation of the virtual destructor is like this: the destructor of the derived class is first called, and then the destructor function of the base class is called. This means that even if the AWOV is an abstract class, the compiler still generates a call to ~ AWOV, so you must provide a function to this function. If not provided, the connector will prompt you to miss the symbol, at this time, you only have to go back.

You can do anything like to do in this function, but as shown in the previous example, you generally don't let it do anything. If this is the case, you may declare the destructor as inline (inline), thereby avoiding the additional overhead brought by the airfinder. This is a very sensible strategy, but you should know the essence of this strategy.

Because your destructor is virtual, its address must be placed in the VTBL of this class, but the inline function assumes that it is not in the independent function (this is the inline meaning, understand?), So you must pay for Special measures to get their address. Terms 33 (Terms 33 Wazely Using Inline Functions - Translation) Provides detailed explanation, but simply says that this is true: if an inline virtual destructive function is declared, you may avoid calling the function. Overhead, but your compiler will still generate an out-limited copy of this function in a place.

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

New Post(0)