7.5 Templates and Inheritance


Templates may be used in many combinations with inheritance. It is possible to combine inheritance and templates because a template class is a class, albeit one with a parameter. Combining these language features allows the parameterization ability of templates to be used in conjunction with the specializing abilities of inheritance. Four combinations of templates and inheritance are presented in this section:

  • a template class that inherits from another template class,
  • a non-template class that inherits from a template class,
  • a template class that inherits from a non-template class, and
  • a template class that uses multiple inheritance.


Each of these combinations will be illustrated by an example showing how the two features are combined and what advantages are possibly gained by using them together.

Inheritance Between Template Classes

In this case, both the base and derived classes are templates. In the usual way, inheritance is used to extend the base-class template through the addition of new methods and/or data in the derived-class template. A common situation in this case is that the template parameter of the derived class is also used as the template parameter of the base class.

An extension of the queue template defined in section 7.1 will be used to illustrate this cominbation of templates and inheritance. A common extension of the queue data structure is the addition of a method allowing the first element in the queue to be returned without being removed from the queue. Such a queue, an InspectableQueue, has all of the methods of a basic queue. It is desirable, therefore, to use inheritance so that the InspectableQueue need not repeat the definition of methods already given in the Queue template class. Also, like the Queue template, the InspectableQueue will be defined as a template so that it can be reused for many different classes. The definition of the InspectableQueue template is shown in the figure below.


Using Inheritance Between Templates
template <class QueueItem> class Queue 
{
   private:

      QueueItem buffer[100];
      int head, tail, count;

    public:
                Queue();
      void      Insert(QueueItem item);
      QueueItem Remove();
               ~Queue();
};

template <class QueueItem> class InspectableQueue : public Queue<QueueItem>
{
   public:
               InspectableQueue();
     QueueItem Inspect();  // return without removing the first element
              ~InspectableQueue();
};

The InspectableQueue template can be elaborated and objects of the elaborated template can be declared as shown in the following code:

   InspectableQueue<Location> locations;
    Location loc1(...);
    Location loc2(...);
    ...
    locations.Insert(loc1);                  // base class method
    locations.Insert(loc2);                  // base class method
    ...
    Location front    = locations.Remove();  // base class method
    Location newFront = locations.Inspect(); // derived class method
    ...


    

When the declaration InspectableQueue<Location> is elaborated, the compiler must also take into account the inheritance between this template class and the template Queue class. The declaration of the InspectableQueue template states that the template parameter given to the InspectableQueue template, Location in this example, should also be used in elaborating the base class. In other words, the inheritance hierarchy implied by the code above is equivalent to:

   InspectableQueue<Location> : public Queue<Location> {...} // suggestive syntax only

This code is not correct C++ syntax but it conveys the spirit of how derived-class template can relate to its base-class template

.

Inheritance from a Template Class

A non-template derived class can inherit from a template base class if the template's parameter is fixed by the nature and definition of the derived class. In this case the template parameter is determined by the designer of the derived class. A programmer using the derived class need not be aware that a template is used in the definition of the derived class. In the normal sense of inheritance, the public methods of the template class will be public methods in the derived class. Thus, the programmer using the derived class can use both the derived- and base-class methods to manipulate a derived-class object. To the programmer using the derived class, the types involved in the inherited operations reflect the template parameter decided upon by the designer of the derived class.

A variation of the Polygon class illustrates a non-template class that inherits from a template class. A polygon shape is defined by a list of points. The list is of unknown length and may change during its lifetime, with new points being added or existing points being removed. A Polygon object should also know how to draw itself on a canvas by drawing a sequence of lines connecting each pair of adjacent points. Thus, the Polygon class is a combination of two capabilities: the capability to maintain a list of points and the capability to draw itself. Instead of implementing all of the methods to maintain a list, the list capability is inherited from the List template where the List template is elaborated with the Location class. The Polygon derived class adds to these inherited methods the capability to draw itself. The design of the Polygon class is shown in the figure below. The List template interface is repeated in this figure for reference.


Polygon Class Inherits from the List Template
template <class BaseType> class List {
   private:
       ...
   public:

               List();
     void      First();               // set current element to first element
     void      Next();                // advance to next list element
     int       Done();                // is there a current element?
     BaseType& Current();             // reply current value
     void      Insert(BaseType val);  // insert after current element
     void      Delete();              // delete current element
    ~List();
  };
class Polygon : public List<Location> // Polygon "is-a" list of Locations
{
   public:
     Polygon();
     void Draw(Canvas& canvas);       // draw itself in a canvas
};

The template parameter is fixed by the Polygon class because the Polygon can only be a List of Locations. The Polygon class is itself not a template, because all of the types in the Polygon class are fixed.

The Polygon class can be used as shown in the following code example. Notice that the Polygon object can be manipulated using both the methods inherited from the template base class (e.g,, Insert) and the methods defined in the Polygon class itself (e.g., Draw). This code example also illustrates that to the programmer using the Polygon class, the presence of a template class is not evident.

    Polygon poly;
     ...
     poly.Insert(Location(20,20));    // inherited from template
     poly.Insert(Location(30,30));    // inherited from template
     // insert other locations
     poly.Draw(canvas);               // derived class method

This code reveals an important part of the Polygon class's design. The Insert method applied to a Polygon requires a Location object as its parameter. The class of the Insert method's argument is determined by the parameter of the List template. Since the Polygon class inherits from List<Location>, the Insert method's argument is required to be a Location object.

Inheritance by a Template Class

In this case, the derived class is a template class, but the base class is a non-template class. Under these circumstances, the template parameter only has meaning to the derived class, not to the base class. As with all other uses of public inheritance, though, the derived class does present in its public interface all of the methods inherited from the base class, plus any other methods added by the derived class.

Illustrating this case is a template that binds an interface, defined by the methods of an abstract base class, and the class whose methods implement the interface. The example revisits the Clock class presented in section 6.10. The Clock class depends only on two methods in the objects to which it was connected: a Next method and a Show method. Instead of binding the Clock class to a specific class hierarchy, such as the class hierarchy whose base class is DisplayableNumber, it is more useful to allow a Clock object to be connected to any othef object that has these two methods. In section 6.10, multiple inheritance was used to combine an abstract base class whose pure virtual methods defined the interface and another class that provided the implementation of the methods required by the abstract base class. That same problem is solved here using templates. The abstract base class and the Clock class defined in terms of this abstract base class are shown below.


The Sequenced Interface and the Clock Class
class SequencedInterface
{
   public:
        virtual void Show() = 0;
        virtual void Next() = 0;
};
class Clock
{ 
private:
        SequencedInterface *sequenced;
public:
        Clock() {}
        void ConnectTo(SequencedInterface& sq) { sequenced = &sq;}
        void ConnectTo(SequencedInterface* sq) { sequenced =  sq;}
        void Notify() { sequenced->Next();
                        sequenced->Show(); }
};

This template uses an object whose class is given by the template's parameter to satisfy the requirements of the abstract base class from which the template inherits. The object is given to the template as a constructor argument, and a reference to this object is retained as part of the template's private data. In the code below, the private data seqItem in the template refers to this object. When the template is asked to perform its Show method, it simply forwards this request to the seqItem object; the same occurs for the Next method. If the class used to elaborate the template does not define a Show or a Next method, the compiler will issue error messages to this effect. Thus, at run-time, the program is guaranteed that the seqItem object does provide the needed Show and Next methods.


Defining the ImplementsSequenced Template by Inheritance from a Non-Template Class
template class <SequencedImplementation> class Sequenced : public SequencedInterface  
{
   private:
         SequencedImplementation& seqItem;
   public:
        Sequenced(SequencedImplementation& item) : seqItem(item) {}
        void Next() { seqItem.Next();}
        void Show() { seqItem.Show();}
};

The template can be used as shown in the following code. Here a Clock and a Counter object are created with the intention of connecting the Clock object to the Counter object. However, the Clock's ConnectTo method requires that the object to which the Clock will be connected is a derived class of SequencedInterface. The Counter class, however, inherits from DisplayableNumber and not from SequencedInterface. By elaborating the ImplementsSequenced template with the Counter class, an indirect relationship is established between the Counter class and the SequencedInterface class through the programming in the Sequenced template. This indirect relationship is established in two steps: first the Sequenced template is elaborated with the Counter class, and then an object of this elaborated template class (timedCounter) is constructed with an object of the Counter class (time). These two steps are shown in the single line of code that creates the timedCounter object:

    Clock timer;
     Counter time(0);
     ...
     Sequenced <Counter> timedCounter(time);
     ...
     timer.ConnectTo( (SequencedInterface&)timedCounter );
     ..
     timer.Notify();  // invokes Next and Show in time object

The critical part of the code above is where the timedCounter id type cast to a SequencedInterface object, which is possible because the timedCounter object is an instance of the class Sequenced<Counter>, whose base class is SequencedInterface. Thus, the derived class object is properly type cast to an object of its base class. This allows the Clock class only to be aware of the fact that the object to which it is connected satisfies the interface defined in the SequencedInterface abstract base class.

The Sequenced template can be elaborated with any class that provides a Show and a Next method. The example was given earlier of an Animation class (section 6.10) that had both of these methods. An Animation object could also be connected to a Clock object, as shown below.

    Clock timer;
     Animation movie(...);
     ...
     Sequenced <Animation> timedAnimation(movie);
     ...
     timer.ConnectTo( (SequencedInterface&)timedAnimation );
     ..
     timer.Notify();  // invokes Next and Show in movie object
 

This code demonstrates the template's value: it can be used to allow objects of any class possessing a Show and a Next method to be type cast safely to a SequencedInterface type, regardless of whether the object's class inherits from the SequencedInterface base class. The type casting is safe because when the template is elaborated the compiler will confirm that the class being used to elaborate the template has the required Show and Next methods. The type casting fulfills its role by allowing the Clock class to remain unaware of the exact class of the objects to which it is connected.

Templates and Multiple Inheritance

Multiple inheritance is used in a variation of the technique in which a template binds an interface defined by an abstract base class to an implementation provided by an arbitrary class. The technique used in the ImplementsSequenced template creates this binding by inheriting from the abstract base class and holding a reference to the implementing class. One drawback of this design is that only the methods defined in the interface are available through the template; it might be necessary that all of the methods of the implementing class be accessible through the template. This requirement can be met by using multiple inheritance.

The figure below shows both the Sequenced template, which uses single inheritance, and the SequencedObject template, which uses multiple inheritance. The Sequenced template uses single inheritance and association to bind the implementation and the interface. The associated object, seqItem, contains the real implementation of the Next and Show methods, while the SequencedObject template uses multiple inheritance. As with the Sequenced template, the SequencedObject template inherits from the abstract base class SequencedInterface. In addition, the SequencedObject also inherits the implementation of its methods from SequencedImplementation. Notice that SequencedImplementation is the template parameter! Both templates require the same constructor argument; an object of the class defined by SequencedImplementation. The Sequenced template maintains an association with this object, and the SequencedObject template uses it to initialize its SequencedImplementation layer. Similarly, the forwarding methods in the Sequenced template forward their invocations to the seqItem object, but the SequencedObject forwards its invocations to the methods in its own base class.


Templates Using Multiple Inheritance
template class <SequencedImplementation> class Sequenced : public SequencedInterface  
{
   private:
         SequencedImplementation& seqItem;
   public:
        Sequenced(SequencedImplementation& item) : seqItem(item) {}
        void Next() { seqItem.Next();}
        void Show() { seqItem.Show();}
};
template class <SequencedImplementation> class SequencedObject 
            : public SequencedImplementation,          public SequencedInterface{
   public:
        SequencedObject(SequencedImplementation& item) : SequencedImplementation(item) {}
        void Next() { SequencedImplementation::Next();}
        void Show() { SequencedImplementation::Show();}
};

From the perspective of the programmer using the Sequenced and SequenceObject templates, they are extremely similar. The code necessary to elaborate the template and construct an object is, except for the difference in the template names, identical. This similarity is illustrated in the figure below, which shows an object being constructed using both templates.


Using the Sequenced and SequencedObject Templates
Clock timer;
Counter time(0);
...
Sequenced <Counter> timedCounter(time);
...
timer.ConnectTo( 
      (SequencedInterface&)timedCounter );
...
timer.Notify();
Clock timer;
Counter time(0);
...
SequencedObject <Counter> timedCounter(time);
...
timer.ConnectTo( 
      (SequencedInterface&)timedCounter );
...
timer.Notify();

However, there is one major difference between the Sequenced and SequencedObject templates. Because the Sequenced template used single inheritance, objects of the Sequenced template can only be type cast to SequenceInterface. In other words, the SequencedImplementation class can only be viewed as a SequencedInterface object. In contrast, the SequncedObject template inherits from both SequencedImplementation and SequencedInterface, which allows SequencedObject objects to be type cast to either a SequencedInterface object or a SequencedImplementation. This flexibility provides the programmer with the ability to type cast a template object as if the object satisfies the SequencedInterface, which it does through inheritance, and to type cast the same template object to the SequencedImplementation class, which is legitimate because the template also inherits from SequencedImplementation.

  1. Design and implement an AppendableList template that inherits from the List template. Your AppendableList template should introduce a method that allows a new element to be appended to the end of the list.

  2. Design and implement PrependableList template that inherits from the List template. Your PrependableList template should introduce a method that allows a new element to be inserted at the front of the list.

  3. Design and implement a template named ActionButton that can be used to create a Button that, upon being pushed, performs an action. The ActionButton template takes a single template argument, OnPushAction. The ActionButton template inherits from the non-template class, Button. The template constructor takes an object of the class OnPushAction. The ActionButton template maintains an association with this object of the OnPushAction class, and adds a single method, void OnPush(), that it forwards to the object with which it is associated.

  4. Design and implement a Named template that can be used to add the property of character-string name to the class that is the template's parameter. Your template should allow a NamedCounter object to be created and manipulated as illustrated below:

        Counter count(0);
    					
    

    Named<Counter> namedCounter(count, "Seconds");

    ...

    if (namedCounter.IsNamed("Seconds")) ...

    char* name = namedCounter.GetName();

    namedCounter.Next();

    ...

    Counter& ctr = (Counter&)namedCounter;

  5. The Named property was added to a class earlier by multiple inheritance and by templates in the exericse above. What are the differences between these two approaches.

  6. Write a test case to verify that an object of the class SequencedObject<Counter> can be type cast to a Counter object while an object of the class Sequenced<Counter> cannot be type cast to a Counter object.

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

 

 

ÿ