The C++Course provides a general introduction to programming in C++. It is based on A.B. Downey's book, How to Think Like a Computer Scientist. Click here for details.


Message Class

An an example of inheritance, we are going to take a message class and create a subclass of error messages. That is, we are going to create a new class called ErrorMessage that will have all the instance variables and functions of a Message, plus an additional member variable, errorCode, which will be displayed when the object is outputted.

The Message class definition looks like this:

class Message
{
  protected:
    pstring source;      //source of message
    pstring message;     //text in message

  public:
    //constructor
    Message(const pstring& src, const pstring& msg) {
      source = src;      //initialize source
      message = msg;     //initialize message
    }

    //convert message to pstring
    virtual pstring getMessage() const {
      return source + ": " + message;
    }
};

And that's all there is in the whole class definition. A Message has two protected member variables: the source of the message and the text of the message. The constructor initializes these member variables from the two pstrings passed as arguments.

You probably noticed that there is a const floating in free-space after the getMessage function declaration. When variables are declared const (such as in the Message constructor), it indicates that the function can't modify their values. However, a const after a function declaration means that the function itself is const! Only member functions can use this feature, because what it means is that the function can't modify any member variables of its class. Think of it as if *this is marked const.

The virtual indicator at the beginning of getMessage is a very important feature of inheritance. When a function is marked virtual, it allows that function to be redefined in subclasses. We will use this feature to change the behavior of getMessage in the ErrorMessage class.

Now here is an example of an ErrorMessage class which extends the functionality of a basic Message:

class ErrorMessage : public Message
{
  protected:
    pstring errorCode;        //error messages have error codes

  public:
    //constructor
    ErrorMessage(const pstring& ec, const pstring& src, const pstring& msg) {
      errorCode = ec;         //initialize error code
      source = src;           //initialize source
      message = msg;          //initialize message
    }

    //convert message to pstring
    virtual pstring getMessage() const {
      return "ERROR " + errorCode + ": " + source + ": " + message;
    }
};

The class declaration indicates that ErrorMessage inherits from Message. A colon followed by the keyword public is used to identify the parent class.

The ErrorMessage class has one additional member variable for an error code, which is added to the string returned from the getMessage function. It would serve to notify a user of the error code associated with whatever message they received. The constructor of ErrorMessage initializes both the original member variables, source and message, and the new errorCode variable.

An important thing to note is that the getMessage function has been redefined in ErrorMessage. Now the returned string includes the error code of the message. Suppose we want to overload the << operator to call getMessage in order to display messages.

ostream& operator << (ostream& os, const Message& msg) {
  return os << msg.getMessage();
}

This function will take any Message object and display it by calling its getMessage function. Since the ErrorMessage class is inherited from the Message class, what that means is that every ErrorMessage object is also a Message object! This allows you to use displayMessage like this:

ErrorMessage error ("1234", "Hard drive", "Out of space");
cout << error << endl;

The code first creates an ErrorMessage object with three strings for the source, message, and error code. Then the error message is passed to <<. Inside <<, the Message object's getMessage function is called in order to get a string representation of the object for output. The resulting output is:

ERROR 1234: Hard drive: Out of space

Even though the function thinks the object is a Message, and has probably never even heard of the ErrorMessage class, it is still calling a function defined in ErrorMessage. This is all because of the virtual keyword used in the getMessage declaration. All functions that are ever going to be redefined in subclasses must be declared virtual. Otherwise, << would not realize the object is an ErrorMessage and would go ahead and call the getMessage defined in Message instead.

As an excercise, remove the virtuals and recompile the program. See if you can predict the output before running it.


Last Update: 2005-12-05