Week 13

Specification Inheritance

In English, the word "inheritance" has several meanings. There is biological inheritance, where you inherit you eye color from your parents, but there is also cultural inheritance and legal inheritance.

A to-do list.

Similarly, in C++ you've seen specialization inheritance, polymorphic inheritance and implementation inheritance. In this lesson, we'll look at another form of inheritance, called specification inheritance.

With specification inheritance, a base class may specify a set of responsibilities that a derived must fulfill, but not provide any actual implementation. The interface (method signatures) are inherited. This is similar to legal inheritance, where your grandparents may leave you some money to be used only for college.

The specification relationship is used in combination with regular specialization:

  • the derived class inherits the interface of the base class, as in specification
  • it also inherits a default implementation of, at least, some of its methods

A derived class may override a virtual member function to add specialized behavior, as we did with Student::toString(), or, it may be required to implement a particular member function, which could not be provided in the base class.

Week 13

Abstract Classes

The classes used in specification inheritance are called abstract classes. The classes we've used so far are called concrete classes. An abstract class is usually an incomplete class, a class that contains certain methods that are specified, but not implemented in the definition of the class.

Because the abstract class contains these incomplete methods, it cannot be used to create objects—it can only be used as a base class when defining other classes. That is, it only makes sense in the context of polymorphic inheritance.

Suppose you have an abstract Shape base class that doesn't have the faintest notion of how to implement its abstract draw() method, yet it knows that each of its concrete derived classes will need to do so.

Only the concrete classes derived from ShapeCircle, Square, and Star—possess the necessary knowledge to actually draw() themselves. You, only need to program in terms of Shape objects; the actual shapes will take care of their own behavior. A Shape class hierarchy.

Week 13

Creating an Abstract Class

Unlike Java and Python, C++ has no abstract keyword. Instead, in C++, an Abstract Base Class (or ABC) is any class that has one, or more, pure virtual member functions, created using the following syntax in the prototype:

class Shape     // abstract class
{
public:
    ...
    // Pure virtual function (abstract method)
    virtual void draw() const = 0;
    ...
};
The Shape UML diagram.

Think of the = 0; part of syntax as a replacement for the abstract keyword in Java and Python.

Abstract classes are not restricted to abstract member functions like draw(); you can have as many regular (concrete) member functions as you'd like, freely mixed with your abstract methods.

The Shape class in the UML diagram at the right has a setLocation() member function. In UML, abstract methods, such as draw(), are drawn using italics.

Your concrete methods may call abstract methods as part of their definition, even though the member function is never implemented in the base class. Unlike Java, C++ pure virtual functions may have an optional implementation. Since you cannot create an instance of an abstract base class, you could only call this implementation from a derived class.

Week 13

Using an Abstract Class

It is illegal to create an instance of an abstract class. Your compiler enforces this. You may, however, create ABC pointers or references as long as they point to, or refer to concrete objects which are derived from the ABC. Trying to instantiate an abstract class

When you extend an abstract class, your derived class must override each and every abstract function in its base class, giving each a concrete implementation. The resulting derived class is a concrete class, and it can be used to create new objects.

Abstract classes thus provide a way of guaranteeing that an object of a given type will understand a given message. In that sense, they specify a set of responsibilities that a derived class mustfulfill.

Week 13

A Triangle Example

Let's look at an example. To create the Triangle (or Circle or Square) classes, using the abstract Shape class as the base class, all you need do is:

class Triangle : public Shape
{
public:
    // MUST override; pure virtual in Shape class
    void draw() const;
};

void Triangle::draw() const { /* your code here */ }
  1. Specify the Shape class as the public base class in the class header.
  2. Provide an implementation for every pure virtual function (abstract method) in the Shape class.
  3. For Triangle that means you must define a draw() member function where indicated by the comments. In reality, you'll probably do a lot more; Circle might have a radius member, the Square class could have members for the size of each side, and the Triangle class could have members for base and height.

Week 13

Redefining Functions

A derived class may replace an inherited member function, which the class designer wanted left alone. In the Person class, getName() returns the name of the person. This works fine as is; there's no reason for a derived class to change it.

However, nothing prevents a derived class from redefining it like this:

class Imposter : public Person
{
public:
    string getName() const 
   { 
        return "Emperor " + Person::getName();
   } 
};

The derived class has no access to the private member name. However, because the getName() member function can be redefined, the derived class was able to effectively gain access to this field. And, because of the principle of substitutability, the Person that your function receives as a parameter may actually be an Imposter.

Week 13

Final Classes

To prevent Imposter classes, when you design a base class, consider which functions you want to allow others to extend and which ones should be "set in stone". No one should ever change getName(), so you can seal it using final like this:

class Person
{
public:
    virtual std::string getName() const final;
};

When a member function is marked final then derived classes are prevented from overriding it and we would see an error message like this:

Person.cpp:33:12: error: virtual function 'virtual
      std::string Imposter::getName() const'
      string getName() const
      ^
Person.cpp:21:8: error: overriding final function
      'virtual std::string Person::getName() const'
      string Person::getName() const { return name; }
      ^

Only virtual functions can be marked final, (which is really annoying). When designing a collection of classes, you normally won't want all of the classes to be extensible. To prevent others from extending your class, add final to the class header, just like you did for the method. If you make a class final, then there is no reason to make the methods final as well.