Effective C ++ 2e Item36

zhaozj2021-02-11  230

Terms 36: Differentiate Interface Inheritance and Realization Inheritance

The concept of (public) inheritance looks simple and further analyzes, it will find that it consists of two can be partially: the inheritance of the inheritance of the function interface and functions. The difference between the two inheritance types and the function declarations discussed in this book is fully consistent.

As the designer of the class, sometimes it hopes to send only the interface (declaration) of the member function; sometimes hope to derive the class also inherit the interface and implementation of the function, but allow derived class rewritments; sometimes, hopes to inherit the interface and implementation, and not Allow derived classes to overwrite anything.

In order to better experience the difference between these selection, look at the following hierarchy, it is used to represent the geometry in a graphics program:

Class Shape {public: Virtual Void Draw () const = 0;

Virtual Void Error (Const String & MSG);

INT ObjectId () const;

...

}

Class Rectangle: Public Shape {...};

Class Ellipse: Public Shape {...};

The pure virtual function DRAW makes Shape become an abstract class. Therefore, the user cannot create an instance of the Shape class, and can only create an instance of its derived class. However, all classes that inherit from Shape are greatly affected by Shape because:

· The interface of the member function will always be inherited. As explained in Terms 35, the meaning of public inheritance is "Yes", so all facts established for the base class must also be established. Therefore, if a function applies to a class, it will also be applicable to its subclass.

Three functions were declared in the Shape class. The first function, DRAW, draws the current object on a screen. The second function, Error, is called by other member functions to report error information. The third function, ObjectID, returns a unique integer identifier of the current object (Terms 17 gives an example of how to use this function). Each function declares in a different way: DRAW is a pure virtual function; error is a simple (non-pure?) Virtual function; ObjectID is a non-virtual function. What does these different statements have any meaning?

First look at the pure virtual function DRAW. The most significant feature of pure virtual functions is that they must re-declare them in any particular classes they inherit, and they often have not defined in an abstract class. Let these two features together, you will recognize:

• Define the purpose of pure virtual functions is that the derived class is just the interface of the inheritance function.

This is very meaningful for the Shape :: Draw function, because all Shape objects can be drawn well, but the Shape class cannot provide a reasonable default for Shape :: DRAW. For example, the algorithm to draw an oval is not the same as the algorithm of the rectangle. For example, the statement above Shape :: DRAW is like a designer that tells subclasses, "You must provide a Draw function, but I don't know how you will implement it."

By the way, it is also possible to provide a definition for a pure virtual function. That is, you can provide implementation for Shape :: DRAW, the C compiler does not block, but the only way to call it is to indicate which call is integrated through class name:

Shape * ps = new shape; // error! Shape is abstract

Shape * ps1 = new rectangle; // correct PS1-> DRAW (); // call Rectangle :: Drawshape * ps2 = new Ellipse; // correct PS2-> DRAW (); // call Ellipse :: DRAW

PS1-> Shape :: Draw (); // Call Shape :: DRAW

PS2-> Shape :: Draw (); // Call Shape :: DRAW

In general, in addition to letting you have a deep impression on your programmer at the cocktail party, you understand this usual effect. However, as will be seen later, it can be applied to a mechanism, providing a default implementation of "safer than general practices" for a simple (non-pure) virtual function.

Sometimes it is useful to declare what the class that is not included in the pure virtual function is useful. Such a procol class, which provides only a function interface for derived classes and is not implemented. The protocol class has been introduced in terms 34 and will mention again in terms 43.

Simple virtual functions and pure virtual functions are a bit different. As usual, the derived class inherits the function of the function, but the simple virtual function is generally provided, and the derived class can choose to change them or do not change them. Think about a moment, you can realize:

· The purpose of the discolition simple virtual function is to make the interface and default implementation of the derived class inheritance function.

Specific to Shape :: Error, this interface is that each class must provide a function that can be called when an error is available, but each class can handle errors in any way they think it is appropriate. If a class does not want to do something special, you can use the default error handling function provided in the Shape class. That is, the statement of Shape :: Error is the designer telling the subclass, "You must support the Error function, but if you don't want to write your own version, you can use the default version in the Shape class."

In fact, it is dangerous to provide a function declaration and default implementation for simple virtual functions. Want to know why, look at the hierarchy of the XYZ airline's aircraft class. XYZ has only two airplanes, Types Type B, and the two models of flight methods are exactly the same. So, XYZ designed such a hierarchy:

Class Airport {...}; // means aircraft

Class AirPlane {public: Virtual Void Fly (Const Airport & Destination);

...

}

Void Airplane :: fly (const airport & desination) {Airplane to a certain destination default code}

Class model: public airplane {...};

Class modelb: public airplane {...};

In order to indicate that all the aircraft must support the FLY function, because of the different models of the aircraft need to be different implementations, AirPlane :: fly is declared as Virtual. However, in order to avoid writing duplicate code in the Model class and ModelB classes, the default flight behavior is provided by the AirPlane :: Fly function, Modela and ModelB inherits this function.

This is a typical object-oriented design. Two classes enjoy a common feature (way of fly), so this common feature is transferred to the base class, and these two classes are inherited to inherit this feature. This design makes the commonality clear, avoiding code repetition, easy to enhance the function, and easy for long-term maintenance - all this is being tangled in object-oriented technology. XYZ is really proud of this. Now, it is assumed that XYZ has issued a fortune and decided to introduce a new aircraft and C. C-type and type A, B types are different, especially, the flight mode is different.

XYZ's programmer adds a class in the hierarchical structure, but they forgot to redefine the FLY function because they are eager to put new aircraft into use.

Class modelc: public airplane {

... // No declaration of FLY function};

Then, in the program, they did something similar to:

Airport jfk (...); // jfk is an airport in New York City

AirPlane * PA = new modelc;

...

PA-> fly (jfk); // Call AirPlane :: fly!

This will result in tragedy: actually trying to let the ModelC object fly like Modeela or ModelB. This behavior can't exchange trust in your visitors!

The problem here is not that AirPlane :: Fly has default behavior, but the modelc can inherit this line without explicitly declaration. Fortunately, it is easy to provide default behaviors for subclasses, while just give them when subclasses want. The trick is to cut off the interface between the virtual function and its default implementation. Here is a method:

Class AirPlane {PUBLIC: Virtual Void Fly (Const Airport & Destination) = 0;

...

Protected: Void Defaultfly (const airport & desination);

Void Airplane :: defaultfly (const airport & destination) {The default code to a destination}

Note AirPlane :: fly has become a pure virtual function, which provides a flight interface. The default implementation still exists in the AirPlane class, but now it is in the form of a single independent function (defaultfly). These classes of Modela and ModelB want to perform the default behavior, only use the DEFAULTFLY in their Fly function, and the interrelationship between the inline and virtual functions, see Terms 33):

Class Model: Public AirPlane {public: Virtual Void Fly (Const Airport & Destination) {defaultfly (destination);

...

}

Class Modelb: Public Airplane {public: Virtual Void Fly (Const Airport & Destination) {DefaultFly (Destination);

...

}

For Modelc classes, it is impossible to inherit incorrect fly implementation. Because the pure virtual function in AirPlane is forced Modelc to provide its own version of FLY.

Class Modelc: Public AirPlane {public: Virtual Void Fly (const airport & desin); ...

}

Void Modelc :: Fly (Const Airport & Destination) {Modelc Trouble to a destination code} This method will not be unlunished (programmers will be wrong because "copy paste"), but it is more reliable than the original design. As for AirPlane :: Defaultfly is declared as protected because it is only the details of AirPlane and its derived class. Users who use AirPlane only care about how to fly without concern.

AirPlane :: Defaultfly is a non-virtual function. Because there is no subclass to redefine this function, the terms 37 illustrates this fact. If defaultfly is a virtual function, it will return to this question: If some subclasses should redefine DefaultFly and forget to do it, what should I do?

Some people oppose the implementation of interfaces and defaults as separate functions, such as Fly and DefaultFly. They believe that at least this will pollute the namespace, because so many similar function names are spreading. However, they still agree with the interface and default implementation should be separated. How to solve the contradiction in this surface? You can use this fact that the pure virtual function must re-declare in subclasses, but it can still have its own implementation in the base class. The following AirPlane is using this redefined a pure virtual function:

Class AirPlane {PUBLIC: Virtual Void Fly (Const Airport & Destination) = 0;

...

}

Void Airplane :: fly (const airport & desination) {Airplane to a certain destination default code}

Class Model: Public AirPlane {public: Virtual Void Fly (Const Airport & Destination) {airplane :: fly (destination);

...

}

Class Modelb: Public AirPlane {public: Virtual Void Fly (Const Airport & Destination) {airplane :: fly (destination);

...

}

Class Modelc: Public Airplane {public: Virtual Void Fly (Const Airport & Destination);

...

}

Void Modelc :: Fly (Const Airport & Destination) {Modelc Trous to a destination code}

This design is almost almost the same, just a pure virtual function airplane :: Fly's function, replaces the independent function airplane :: defaultfly. In essence, Fly has been divided into two basic parts. It declares its interface (derived class must be used), and its definition shows its default behavior (derived class may be used, but to clearly request). However, after the Fly and DefaultFly are merged, they will no longer declare the different protection levels for these two functions: Originally a protected code (in DefaultFly) is now public (because it is in fly).

Finally, let's talk about Shape's non-virtual functions, ObjectId. When a member function is a non-virtual function, it should not be different in derived classes. In fact, non-virtual member functions indicate a particularly invariant because it represents a behavior that will not change - no matter how special a derived class. and so,

· The purpose of statement of non-virtual functions is to make the interface and mandatory implementation of the derived class inheritance function. It can be considered that the statement of Shape :: ObjectID is to say, "Each Shape object has a function to generate an identifier of the object, and the way the object identifier is always the same. This method is composed of Shape :: ObjectID Definition decisions, derived classes can't change it. "Because the non-virtual function represents a particular nature, it will never redefine in subclasses, and discuss this clause 37.

Understand the difference in pure virtual functions, simple virtual functions and non-virtual functions in the statement, you can accurately designate what you want to let the derived class inherit: Just interface, or interface and a default implementation? Or, interface and a forced implementation? Because these different types of declarations refer to all things, they must be carefully selected when declaring member functions. Only do this can avoid two errors that have not experienced programmers.

The first error is to declare all functions as non-virtual functions. This makes the derived class without specialization; non-false aromal functions, especially problems (see Terms 14). Of course, the design of the design is not prepared as a base class is also completely reasonable (Terms M34 gives an example you will do this). In this case, it is appropriate to declare a set of non-virtual member functions. However, all functions are declared as non-virtual functions, mostly because of ignorance between virtual functions and non-virtual functions, or too much fear of virtual functions on program performance (see Terms M24). In fact, almost any kind of class used as a base class has a virtual function (see Terms 14 again).

If you worry about the overhead of the virtual function, please allow me to introduce 80-20 law (see Terms M16). It indicates that in a typical program, 80% of the running time is spent on a 20% code. This law is very important because it means that the average, 80% of the function call can be a virtual function, and they will not affect the overall performance of the program. So, before worried about whether you have a virtual function, you may wish to focus on that 20% of the code that truly affects the impact.

Another common problem is to declare all functions as virtual functions. Sometimes this is not wrong -, for example, protocol class is evidence (see Terms 34). However, doing so often shows the courage to indicate the firm position. Some functions cannot be defined in the derived class, as long as this is the case, it is necessary to declare it as a non-virtual function. Can't let your function seem to do anything for anyone ---- just redefine all functions as long as they take time. Remember, if there is a base class B, a derived class D, and a member function MF, then the following to the MF call must be normal:

D * pd = new d; b * pb = pd;

Pb-> mf (); // Call MF through base class pointer

PD-> mf (); // Call MF through the derived class pointer

Sometimes, MF declarations must be non-virtual functions to ensure that everything works in the way you expect (see Terms 37). If you need a particularity of the invariance, you will say it!

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

New Post(0)