Effective C++ 笔记 —— Item 34: Differentiate between inheritance of interface and inheritance of implementation.
The seemingly straightforward notion of (public) inheritance turns out, upon closer examination, to be composed of two separable parts: inheritance of function interfaces and inheritance of function implementations.
Consider a class:
class Shape { public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; // ... }; class Rectangle: public Shape { /*...*/ }; class Ellipse: public Shape { /*...*/ };
The purpose of declaring a pure virtual function is to have derived classes inherit a function interface only.
Incidentally, it is possible to provide a definition for a pure virtual function.That is, you could provide an implementation for Shape::draw, and C++ wouldn't complain, but the only way to call it would be to qualify the call with the class name:
Shape *ps = new Shape; // error! Shape is abstract Shape *ps1 = new Rectangle; // fine ps1->draw(); // calls Rectangle::draw Shape *ps2 = new Ellipse; // fine ps2->draw(); // calls Ellipse::draw ps1->Shape::draw(); // calls Shape::draw ps2->Shape::draw(); // calls Shape::draw
The purpose of declaring a simple virtual function is to have derived classes inherit a function interface as well as a default implementation.
class Shape { public: virtual void error(const std::string& msg); // ... };
It turns out that it can be dangerous to allow simple virtual functions to specify both a function interface and a default implementation.
class Airport { /*...*/ }; // represents airports class Airplane { public: virtual void fly(const Airport& destination); // ... }; void Airplane::fly(const Airport& destination) { // default code for flying an airplane to the given destination } class ModelA: public Airplane { /*...*/ }; class ModelB: public Airplane { /*...*/ };
But what if you forget to redefine the fly function:
class ModelC: public Airplane { // ... // no fly function is declared };
In their code, then, they have something akin to the following:
Airport PDX( /*...*/ ); // PDX is the airport near my home Airplane *pa = new ModelC; // ... pa->fly(PDX); // calls Airplane::fly!
The problem here is not that Airplane::fly has default behavior, but that ModelC was allowed to inherit that behavior without explicitly saying that it wanted to. Fortunately, it's easy to offer default behavior to derived classes but not give it to them unless they ask for it. The trick is to sever the connection between the interface of the virtual function and its default implementation. Here's one way to do it:
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 }
Notice how Airplane::fly has been turned into a pure virtual function. That provides the interface for flying. The default implementation is also present in the Airplane class, but now it's in the form of an independent function, defaultFly. Classes like ModelA and ModelB that want to use the default behavior simply make an inline call to defaultFly inside their body of fly (but see Item 30 for information on the interaction of inlining 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 the ModelC class, there is no possibility of accidentally inheriting the incorrect implementation of fly, because the pure virtual in Airplane forces ModelC to provide its own version of fly.
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 }
Some people object to the idea of having separate functions for providing interface and default implementation, such as fly and defaultFly above. For one thing, they note, it pollutes the class namespace with a proliferation of closely related function names. Yet they still agree that interface and default implementation should be separated. How do they resolve this seeming contradiction? By taking advantage of the fact that pure virtual functions must be redeclared in concrete derived classes, but they may also have implementations of their own. Here’s how the Airplane hierarchy could take advantage of the ability to define a pure virtual function:
class Airplane { public: virtual void fly(const Airport& destination) = 0; // ... }; void Airplane::fly(const Airport& destination) // an implementation of a pure virtual function { // default code for flying an airplane to 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 }
When a member function is non-virtual, it’s not supposed to behave differently in derived classes. In fact, a non-virtual member function specifies an invariant over specialization, because it identifies behavior that is not supposed to change, no matter how specialized a derived class becomes. As such:
- The purpose of declaring a non-virtual function is to have derived classes inherit a function interface as well as a mandatory implementation.
Things to Remember:
- Inheritance of interface is different from inheritance of implementation. Under public inheritance, derived classes always inherit base class interfaces.
- 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.