Section 4 - Abstract Classes and Pure Virtual Functions
6A.4.1 Abstract Classes
Let's say that we want to create a mail messaging client, but don't want to commit to only written messages. Some messages might be video or audio as well. Some may be GUI, while others console-based.
Nevertheless there are certain fundamental functions that our message class should provide. One is composing a message and another is displaying a message.
To this end, we will create a prototype class for our system but not commit to a definition of either composing or displaying. Here it is:
// abstract class -------------------------------- class Message { protected: string theMessage; static const string DEFAULT; public: Message(string s = DEFAULT) : theMessage(s) { } virtual void compose() = 0; virtual void display() = 0; }; const string Message::DEFAULT = " -- no message -- ";
We have a constructor and two virtual functions. However, the virtual functions are not defined (this is not a prototype, but the entire definition of class Message). The fact that they are not going to be defined is denoted by tagging the suffix " = 0" onto the end of the two virtual function prototypes, compose() and display(). By this simple notation:
virtual void compose() = 0;
we are declaring that we have no intention of defining the member function compose() anywhere in the class Message. This is called a pure virtual function. Its job is not so much to enable base class Message pointers to point to derived class objects, something we featured in our prior example of virtual functions, but instead to require that all subclasses of Message have their own explicit implementations of compose() (and also display()).
By including even one pure virtual function in our base class, we are implicitly creating an abstract class. This is a class intended as a basis for subclassing. In fact, we can only derive from this class: we cannot instantiate a Message object directly. If you try, you will get a compiler error. So what good is it?
Here is how we might subclass a usable console-based message class from our abstract Message class:
// real class -------------------------------- class ConsoleMessage : public Message { public: ConsoleMessage(string s = DEFAULT) : Message(s) { } void compose(); void display(); }; // ConsoleMessage method definitions -------------------------- void ConsoleMessage::compose() { cout << "Please enter your message here: "; getline(cin, theMessage); } void ConsoleMessage::display() { cout << "Here is the Message ------------------" << endl; cout << theMessage << endl; cout << "End of Message -----------------------" << endl; }
The ConsoleMessage has two properties:
- It is a subclass of Message.
- It defines both pure virtual functions compose() and display().
Whenever you subclass from an abstract class you must (unless you intend the subclass to also be abstract) give definitions for, i.e., override, all of the pure virtual functions contained in the base class. This is considered a good thing: You cannot subclass from an abstract class and forget to define (i.e., override) a crucial method.
Any methods that are not declared pure virtual in the base class (by the special notation func() = 0;) will not need to be overridden. For one thing, the base class will have defined these methods already. It is only the pure virtual methods for which you can and do omit the definitions in the base class and which therefore must be defined in any derived class.
Question: Why does the keyword protected appear before the instance variable theMessage?
Protected is a way of making data off-limits to most classes, much as the keyword private makes such members inaccessible. However protected is a bit more permissible: It allows any of the subclasses which are derived from class Message to have direct access to the data, as we would expect they will need. All other classes will not be able to modify or even access this data.
It is possible to create an abstract class that
- has no data, and
- contains function prototypes which are all pure virtual functions.
Such a class is informally called an interface, since its job is to provide interface method signatures that a client will use to interact with any of its derived classes.