Inheritance of interface is different from inheritance of implementatino.
Under public inheritance, derived classes always inherit base class interfaces, but may act differently in terms of base class implementation inheritance:
- Pure virtual functions specify inheritance of interface only
- Simple (impure) virtual functions specify inheritance of interface plus inheritance of a default implementation
- Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation
To show the implementation differences above, we can define following classes for an example:
|
|
Since derived classes Rectangle
and Ellipse
are both public inherited, according to item 32, this means is-a
, so anything that is true of the base class must also apply to derived classes. Thus, the member function interfaces are always inherited.
Pure virtual functions
For pure virtual functions, there are two features we have to note:
- they must be redeclared by any concrete class that inherits them
- they typically have no definition in abstract classes
From these feature, we can conclude that:
The purpose of declaring a pure virtual function is to have derived classes inherit a function interface only.
Here, the declaration of Shape::draw
says to the client of the Shape
that, “you have to provide a draw
function, but I don’t know how you’re going to implement it.”
Incidentally, C++ allows us to provide an implementation for Shape::draw
(example: pure virtual destructor in item 7). However, the only way to call it would be to qualify the call with the class name:
|
|
This feature is generally of limited utility, except that it can be employed as a mechanism for providing a safer-than-usual default implementation for simple virtual functions as we’ll see below.
Simple (impure) virtual functions
Simple virtual functions provide an implementation that derived classes may override, which means that
The purpose of declaring a simple virtual function is to have derived classes inherit a function interface as well as default implementation.
For example, the declaration of error
function tells us that “You have to supoort an error
function, but if you don’t want to write your own, you can fall back on the default version in the Shape
class.”
Potential danger
However, in the perspective of class design, there’s a potential danger to allow simple virtual functions to specify both a function interface and a default implementation. That is: a derived class is allowed to inherit the default implementation without explicitly saying that it wanted to. For example:
|
|
Suppose both ModelA
and ModelB
inherit the base class Airplane
without re-implementing the simple virtual function fly
. Chances are that ModelB
is actually a new type of model, yet its programmer simply forgets to redefine the fly
function.
Separate interface from default implementation
To make our design more foolproof, we may separate functions for providing interface and default implementation, such as below:
|
|
|
|
|
|
Take use of pure virtual function
Some people may feel this design is redundant, arguing that this will polllute the class namespace with a proliferation of closely related function names. Then we may take advantage of the fact that pure virtual functions, which insists on redeclaring in concrete derived classes, may also have implementations of their own:
|
|
|
|
|
|
In essence, this design breaks fly
into two fundamental components:
fly
’s declaration specifies its interface, which derived classes must usefly
’s definition specifies its default behavior, which derived classes may use by explicitly request it
However, in merging fly
and defaultFly
, we have lost the ability to give the two functions different protection levels: previously protected
code in defaultFly
is now public
in fly
.
Non-virtual function
A non-virtual member function specifies an invariant over specialization (a point discussed in item 36), identifying behavior that is not supposed to change, no matter how specialized a derived class becomes. Thus:
The purpose of declaring a non-virtual function is to have derived classes inherit a function interface as well as a mandatory implementation.
The declaration for Shape::objectId
is basically say, “Every Shape
object has a function that yields an object identifier, and that identifier is always conputed the same way. That way is determined by the definition of Shape::objectID
, and no derived class should try to chagne how it’s done.”
Summary
The differences in declarations for pure virtual, simple virtual and non-virtual functions allow us to specify with precision what we want derived classes to inherit: interface only, interface and a default implementation, or interface and a mandatory implementation, respectively.