[ Team LiB ] |
![]() ![]() |
Gotcha #71: Hiding Nonvirtual FunctionsA nonvirtual function specifies an invariant over the hierarchy (or subhierarchy) rooted at the base class. Derived class designers cannot override nonvirtual functions and should not hide them. (See Gotcha #77.) The rationale for this rule is basic and straightforward: to do otherwise would defeat polymorphism. A polymorphic object has a single implementation (class) but many types. From our knowledge of abstract data types, we know that a type is a set of operations, and these operations are represented in an accessible interface. For example, a Circle is-a Shape and should work in an unsurprising and consistent fashion with code written to either of its interfaces: class Shape { public: virtual ~Shape(); virtual void draw() const = 0; void move( Point ); // . . . }; class Circle : public Shape { public: Circle(); ~Circle(); void draw() const; void move( Point ); // . . . }; The designer of Circle has decided to hide the base class move function (perhaps the base class assumes that the Point is an upper corner, but the version for Circle uses the center). Now it's possible for the same Circle object to behave differently, depending on the interface used to access it:
void doShape( Shape *s, void (Shape::*op)(Point), Point p )
{ (s->*op)( p ); }
Circle *c = new Circle;
Point pt( x, y );
c->move( pt );
doShape( c, &Shape::move, pt ); //oops!
Hiding a base class nonvirtual function raises the complexity of using a hierarchy without providing any compensating merit: class B { public: void f(); void f( int ); }; class D : public B { public: void f(); // bad idea! }; B *bp = new D; bp->f(); // oops! called B::f() for D object D *dp = new D; dp->f( 123 ); // error! B::f(int) hidden Virtual and pure virtual functions are the mechanisms used to specify type-variant implementations. With virtual functions, overriding in the derived class assures that only a single implementation—and therefore a single set of behaviors—will be available for a particular object at runtime. Therefore, the behavior of the object is not dependent on the interface used to access it. As an aside, note that virtual functions can be called in a nonvirtual manner through use of the scope operator, but this is a property of the use of the interface, not its design. However, in this sense, an overridden base class virtual function is still available to its derived classes: class Msg { public: virtual void send(); // . . . }; class XMsg : public Msg { public: void send(); // . . . }; // . . . XMsg *xmsg = new XMsg; xmsg->send(); // call XMsg::send xmsg->Msg::send(); // call hidden/overridden Msg::send This is a sometimes-necessary hack, not a design. However, the availability of a nonvirtual call to an overridden base class virtual function can rise to the level of a design. Such a call is commonly used to provide a shared, basic implementation in the base class for overriding derived class functions. A standard implementation of the Decorator pattern is one common illustration of this approach. The Decorator pattern is used to augment, rather than replace, the existing functions of a hierarchy:
class MsgDecorator : public Msg { public: void send() = 0; // . . . private: Msg *decorated_; }; inline void MsgDecorator::send() { decorated_->send(); // forward call } The class MsgDecorator is an abstract class, since it declares a pure virtual send function. Concrete classes derived from MsgDecorator must override the pure virtual MsgDecorator::send. However, even though it can't be called as a virtual function (except in unusual, nonstandard, and typically erroneous circumstances; see Gotcha #75), MsgDecorator::send may be invoked in a nonvirtual manner through use of the scope operator. The implementation of MsgDecorator::send provides a common, shared implementation that all overriding derived class sends must implement. They do this through a nonvirtual call:
void BeepDecorator::send() { MsgDecorator::send(); // do base class functionality cout << '\a' << flush; // additional behavior . . . } An alternative might be for the MsgDecorator class to declare a protected nonvirtual function containing the common functionality, but the use of a defined pure virtual function more clearly indicates its intended use by derived class functions. |
[ Team LiB ] |
![]() ![]() |