(Click here, next to the previous article)
Now, assuming that XYZ's wealth has increased, it is decided to introduce a new model, Model C. Model C is different from model A and model B in some aspects. In particular, its flight is different.
Programmers at XYZ added the class of Model C to hierarchy (inheritance system), but they forgot to redefine the fly function as they rushed to put new models into service:
Class modelc: Public airplane {
... // No Fly function is declared
};
Therefore, in their code, something similar to this occurs:
Airport PDX (...); // PDX is the airport near my home
Airplane * pA = new modelc;
...
Pa-> fly (PDX); // callairplane: fly!
This is a disaster: an attempt to make a modelc object fly like a modela or modelb. This is not an encouraging behavior in the travel crowd.
The problem here is not that airplane: fly has a default behavior, but that modelc is allowed to inherit this line without specifying what it wants to do. Fortunately, it is easy to "provide default behavior for derived classes (derived classes), but they will not be handed over to them unless they make clear requirements. This trick is to cut off virtual functions.Interface(Interface) and its defaultImplementation(Implemented by default. This method is used as follows:
Class airplane {
Public:
Virtual void fly (const airport & Destination)= 0;
...
Protected:
Void defaultfly (const airport & Destination);
};
Void airplane: defaultfly (const airport & Destination)
{
Default Code for flying an airplane to the given destination
}
Note airplane: How is fly converted intoPureVirtual function. It provides an interface for flight ). The default implementation will also appear in airplane class, but now it is an independent function, defafly fly. For example, if modela and modelb need to use the default behavior classes, you only need to make an inline call to defaultfly in their fly function bodies (but refer to the inline and virtual Functions) ):
Class modela: 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 class, it is impossible to inherit the incorrect fly implementation accidentally, because the pure virtual (pure virtual) in airplane forces modelc to provide its own fly version.
Class modelc: Public airplane {
Public:
Virtual void fly (const airport & Destination);
...
};
Void modelc: Fly (const airport & Destination)
{
Code for flying a modelc airplane to the given destination
}
This solution is not very secure (programmers can still use copy-and-paste to put themselves in trouble), but it is more reliable than the original design. As for airplane: defaultfly, it is protected because it is completely the Implementation Details of airplane and Its Derived classes (derived class. Customers who use airplanes should only care about how they can fly, rather than how they can fly.
Airplane: defaultfly isNon-virtualFunction (non-virtual function) is also important. This is because derived class (derived class) should not redefine this function, which is a principle introduced in item 36. If defafly fly is virtual, you will encounter a loop problem: what if some Derived classes (derived classes) should redefine defaultfly but forget it?
Some people oppose providing functions for interfaces (interfaces) and default implementation (default implementations), just like fly and defaultfly above. First, they noticed that this would cause similar related function names to pollute class namespace. However, they still agree that the interface and default implementation should be separated. How did they solve this apparent conflict? By taking advantage of the fact that pure virtual functions (pure virtual functions) must be redeclared (re-declared) in concrete Derived classes (a specific derived class), they can also have their own implementations. Here is how airplane hierarchy uses this capability to define a pure virtual function ):
Class airplane {
Public:
Virtual void fly (const airport & Destination)= 0;
...
};
Void airplane: Fly (const airport & Destination)// An implementation
{// A pure virtual function
Default Code for flying an airplane
The given destination
}
Class modela: 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)
{
Code for flying a modelc airplane to the given destination
}
In addition to replacing the independent function airplane: defaultfly with the pure virtual function (pure virtual function) airplane: Fly function, this design is almost identical to the previous one. Essentially, fly can be split into two basic components. Its declaration (Declaration) specifies its interface (Interface) (which is required by Derived classes (derived class), and its definition (Definition) specify its default behavior (this is usable by the derived classes (derived class), but only when they explicitly require this ). Merge fly and defaultfly. In any case, you lose the ability to give these two functions different protection levels: the original protected code (implemented by ultfly) now it becomes public (because it is located in Fly ).
Finally, let's look at the shape non-virtual function (non-virtual function), objectid:
Class shape {
Public:
Int objectid () const;
...
};
When a member function (member function) is non-virtual (non-virtual), it should not be expected to behave differently in the derived classes (derived class. In fact, a non-virtual member function (non-virtual member function) specifiesInvariant over Specialization(Beyond the specialized invariant), because no matter how special a derived class (derived class) becomes, it regards it as an action that cannot be changed. Except as follows,
- The purpose of declaring a non-virtual function is to have derived classes inherit a function.Interface as well as a mandatory implementation(So that the derived class inherits both the interface of a function and a forced implementation ).
You can consider the shape: objectid declaration as follows: "Each shape object generates an object identifier (Object ID code), and this object identifier (Object ID code) this method is always calculated using the same method. This method is determined by the shape: objectid definition, and the derived class (derived class) should not try to change it." Because a non-virtual function (non-virtual function) is considered asInvariantOver specialization (the invariant beyond the specialization) should never be redefined in derived class (derived class). For details, see item 36.
The difference between the declaration of pure virtual, simple virtual, and non-virtual functions allows you to specify exactly what you need to inherit from the derived classes (derived class. Interface only (only interface), interface and a default implementation (interface and a default implementation), and interface and a mandatory implementation (interface and a mandatory implementation ). Because these different declaration types mean that they have different meanings. When you declare your member functions (member functions), you must carefully choose between them. If you do this, you should be able to avoid the two most common errors caused by inexperienced class designers.
The first error is to declare all functions as non-virtual (non-virtual ). This does not leave space for the specialization of derived classes (derived classes); Non-virtual Destructors (non-virtual destructor) is particularly problematic (see item 7 ). Of course, there is a reason to design a class that is not used as a base class (base class. In this case, a set of exclusive non-virtual member functions (non-virtual member functions) is completely reasonable. However, in general, such classes may be ignorant of the differences between virtual and non-virtual functions (non-virtual functions, it may also be the result of no justification for the performance cost of virtual functions. In fact, almost any class used as a base class (base class) will have virtual functions (or refer to item 7 ).
If you are concerned about the cost of virtual functions, please allow me to introduce experience-based 80-20 Rules (see item 30). In a typical program, 80% of the running time is spent on executing 20% of the Code. This rule is very important because it means that, on average, 80% of your function calls can be virtualized without a slight or perceptible impact on the overall performance of your program. Before you step into the shadow of worries about "can you afford a virtual function (virtual function) Cost", you should use some simple preventive measures, to ensure that you are concerned with the 20% that can produce a decisive difference in your program.
Another common error is that all member functions (member functions) are virtual ). Sometimes this is correct-the interface classes (Interface Class) of item 31 can be used as evidence. However, it may also be a sign of a class designer who lacks the determination to express his attitude. Some functions should not be redefined in Derived classes (derived class). In this case, you should declare those functions as non-virtual (non-virtual) and clearly express this. It does not serve those people. They assume that if they only need to spend some time redefining all your functions, your class will be used by all people to do everything, if you have an invariant over specialization (beyond the specialization invariant), simply put, don't be afraid!
Things to remember
- Inheritance of Interface (interface inheritance) is different from inheritance of implementation (Implementation inheritance. In public inheritance (Public inheritance), derived classes (derived class) always inherits base class interfaces (base class interface ).
- Pure virtual functions (pure virtual function) specifies the inheritance of interface only (only the interface is inherited ).
- Simple (impure) virtual functions (simple virtual function) specifies the inheritance of Interface (interface inheritance) plus inheritance of a default implementation (default implementation inheritance ).
- Non-virtual functions (non-virtual function) specifies the inheritance of Interface (interface inheritance) plus inheritance of a mandatory implementation (forced inheritance ).