In the example of the employee-database program, the Employee class defines a do-nothing computePay function. This solution is somewhat inelegant, since the function is intended never to be called.
A better solution is to declare computePay as a “pure virtual function.” This is done by specifying an equal sign and a zero after the member function's prototype, as follows:
class Employee
{
public:
// ...
virtual float computePay() const = 0; // Pure virtual
};
A pure virtual function requires no definition; you don't have to write the body of Employee::computePay. It is intended to be redefined in all derived classes. In the base class, the function serves no purpose except to provide a polymorphic interface for the derived classes.
You cannot declare any instances of a class in which a function is declared as pure virtual. For example, since computePay is now a pure virtual function, you cannot declare any objects of type Employee.
This restriction is necessary to prevent anyone from calling a pure virtual function for an object. If you could declare generic Employee objects, you could call computePay for it, which would be meaningless. You can only declare objects of the derived classes which provide a definition for computePay.
A class that defines pure virtual functions is known as an “abstract class,” because you cannot declare any instances of it. (Classes that you can declare instances of are sometimes called “concrete classes.”) It is legal, however, to declare pointers to an abstract class. For example, you can declare Employee pointers and use them for manipulating objects of derived classes. This is the way computePayroll and the other employee-database functions work.
If a derived class does not provide a definition for a pure virtual function, the function is inherited as pure virtual, and the derived class becomes an abstract class too. This does not happen with ordinary virtual functions, because when a derived class omits a definition of an ordinary virtual function, it uses the base class's version. With pure virtual functions, this is impossible, since the base class doesn't have a version. Thus, if WageEmployee did not define a version of computePay, it would be an abstract class too.
It's common to write a class hierarchy consisting of one or more abstract classes at the top which act as base classes for the concrete classes at the bottom. You cannot derive an abstract class from a concrete class.
Sometimes it's useful to write an abstract class that has little or no data members or code, consisting primarily of pure virtual functions. Most of the data and the code for the functions is defined when a new class is derived from such a base class. This is desirable when the base class's interface embodies a set of properties or operations that you'd like many other classes to have, but whose implementations differ for each class.
For example, consider a SortedList class that can store objects of any class.
class SortedList
{
public:
SortedList();
void addItem( const SortableObject &newItem );
// ...
private:
// ...
};
A SortedList object stores pointers to objects of class SortableObject. This is an abstract class that has pure virtual functions named isEqual and isLessThan:
class SortableObject
{
public:
virtual int isEqual( const SortableObject &other ) const = 0;
virtual int isLessThan( const SortableObject &other ) const = 0;
};
If you want to store names in a SortedList, you can derive a class called SortableName from SortableObject. You can then implement isEqual and isLessThan to perform string comparisons. For example:
class SortableName : public SortableObject
{
public:
int isEqual( const SortableObject &other ) const;
int isLessThan( const SortableObject &other ) const;
private:
char name[30];
};
int SortableName::isEqual( const SortableObject &other ) const
{
return (strncmp( name, other.name, 30 ) == 0);
};
// Similar implementation for isLessThan
Similarly, if you want to store ZIP codes, you can derive a class SortableZIP from SortableObject and implement the member functions to compare numbers. SortableObject thus provides a template for you to use when writing your own classes. By itself, SortableObject isn't a useful class, because it contains no code or data. You supply those when you derive a class from it.