Inheritance/Polymorphism  «Prev 

Abstract Classes and Pure Virtual Functions

A base class reference or pointer can be assigned a derived class object or address. Manipulation of such a reference or pointer can be polymorphic by using virtual functions, the properly overridden function defined in the derived class is called dynamically. Usually, such a base class is abstract. This identifies the class as an important type to be used polymorphically. It guarantees that the compiler will insist on overridden member function definitions where concrete behavior for derived types is needed.

Virtual Functions

To use heterogeneous lists effectively, we need functions that can be applied to any object on the list without regard to its class. Such functions will generally have different definitions for different classes. We want the appropriate definition to be used whenever a function is applied to an object, even though the class of the object was not known at compile. In C++ these capabilities are provided by virtual functions.

Should an overridden virtual function throw an exception?

Only if the base class says it might. It is appropriate for an override to throw an exception if and only if the specification of the member function in the base class says it might (there is a special case for legacy code that considers the absence of an exception specification in the base class to allow the derived class to throw whatever it wants, but this is for historical reasons only). Without such a specification in the base class, throwing an exception in an override is similar to false advertising. For example, consider a user-car salesman selling a kind-of car that blows up (that is, throws an exception) when you turn on the ignition switch. Code should do what the specification says it will do, or other code that relies on those promises may break. Also, consider the Ostrich/Bird dilemma. Suppose Bird::fly() promises never to throw an exception, as follows.

#include <iostream>
using namespace std;
class Bird {
public:
Bird() throw();
virtual ~Bird() throw();
int altitude() const throw();
virtual void fly() throw();
// PROMISE: altitude() will return a number > 0; never throws an exception.
protected:
int altitude_;
};
Bird::Bird() throw()
: altitude_(0)
{ }
Bird::~Bird() throw()
{ }
int Bird::altitude() const throw()
{ return altitude_; }
void Bird::fly() throw() { altitude_ = 100; }

Based on this promise, it is legitimate and appropriate to assume that the fly() member function will not throw an exception. For example, the following sample code is decorated with a throw(), meaning that this function promises not to throw an exception.
void sample(Bird& bird) throw() // Legitimate reliance on what
// Bird::fly() says
{
bird.fly();
}
But suppose Ostrich::fly() is defined to throw an exception, as follows.
 class CannotFly { };
class Ostrich : public Bird {
public:
virtual void fly() throw(CannotFly);
// PROMISE: throws an exception despite what Bird says
};
void Ostrich::fly() throw(CannotFly)
{ throw CannotFly(); }
 

Now suppose someone legitimately passes an Ostrich into the sample() code:
 int main()
{
Ostrich bird;
sample(bird); // Legitimate conversion from Ostrich to Bird
}
 

member function throws an Exception

Unfortunately the program will crash in the sample() function, since the fly() member function ends up throwing an exception. One cannot blame main() for passing an Ostrich into the sample() function; after all, Ostrich inherited from Bird and therefore Ostrich is supposed to be substitutable for Bird. One cannot blame sample() for believing the promise made by Bird::fly(); indeed programmers are supposed to rely on the specification rather than the implementation. So the blame rests with the author of class Ostrich, who claimed that Ostrich was substitutable for Bird even though it didn not behave like a Bird. The lesson is that improper inheritance cannot be fixed by throwing an exception if the base class prohibits the throwing of an exception. This is because the root of improper inheritance is behavior that violates a contract, and throwing an exception is part of a function's behavior. Specifically, the behavior of an overridden virtual function that throws an exception conflicts with a base class contract that prohibits the throwing of an exception.