Resources

Abstract classes and multiple inheritance in C++

Aims

To enable students to:

  1. handle abstract classes

  2. call inherited constructors

  3. make friends

  4. use multiple inheritance

Handle abstract classes

Frequently classes within hierarchies do not represent anything concrete enough to be instantiated in their own right. Such a class is a 'holder' for methods and attributes shared between derived classes. Within a large hierarchy there may be several abstract classes in existence at different points in the structure.

For example, in an 'animal' hierarchy there will probably be several layers of abstraction. The beginning may be a base 'animal' class containing attributes and methods common to all animals. From here more specific, but still abstract, classes can be derived - such as mammals and birds. Only specific classes at the bottom of the hierarchy represent concrete types of animals.

Use of the virtual keyword can ensure that abstract classes can not be instantiated within a program. Methods within a class may be marked as being 'pure virtual functions' by the following syntax within the class definition:

virtual data type method_name () = 0;

This means no implementation is provided or the method within this class, implying:

  1. The method can not be invoked by objects of derived classes, unless an overriding implementation is provided

  2. No objects of the class containing the virtual method may be instantiated

As an example, an animal class could be represented by

class CAnimal
{

public:

virtual void move (int iDirection) = 0;

...

}

And a specific creature could be represented as follows, not in this case several other classes (such as mammal and pig) have been passed through to reach this point:

class CSaddleBack:: public CPig
{
public:

void move (int iDirection);

...

}

Call inherited constructors

A derived class always calls the constructor of the base class besides its own. The base class constructor is always called first, followed by the derived class, and so on down the tree if there are several levels of inheritance.

If the base class takes no parameters then the constructor inheritance is implicit, but if parameters are involved then they must be stated explicitly in each derived class.

In the following example, a fragment of a customer class takes the customers name as an argument to its constructor.

Class CCustomer
{
private:
char customer_name[30];

public:
CCustomer (char *name_in)
{
strncpy(customer_name, name_in, 29);

customer_name[29]='\0'
}
}
}

Any constructors of the derived class must pass a value to the constructor of CCustomer. In the following example CAccountCustomer takes both an account number and customer name. Note, the colon operator is used to indicate inheritance.

class CAccountCustomer : public Customer
{
private:
int account_number;


public:
CAccountCustomer (int ACNumber, char *name_in);
}

CAccountCustomer::CAccountCustomer (int ACNumber, char *name_in) : Customer(name_in)
{

...

}

Derived classes also inherit base class destructors, but they are called in the reverse order to constructors. Since class destructors take no arguments, explicit parameter inheritance is not an issue.

Make friends

Member functions and variables declared as private to a class are normally viewed as being inaccessible to objects of other classes. However, it is possible to break this rule via the friend qualifier.

A declaration of the form:

friend class CGeorge;

allows all members of the class CGeorge to access both protected and private members of the class in which the declaration is placed, for example:

class CSue
{
friend class CGeorge;

public:

...

}

declares that CGeorge is viewed as CSue as a friend, though it says nothing about how CGeorge views CSue.

It is possible to restrict access to specific functions in another class by giving a declaration of the form:

friend CSally::peek (int);

which grants only the peek function of class CSally access to the classes protected and private members.

Use multiple inheritance

Inheritance is based on 'a kind of' relationships, so that if class A inherits from class B, it can be said that class A 'is a kind of' B. As an example an oak is 'a kind of' tree, this is an example of single inheritance where a class is derived from a single base class.

Sometimes single inheritance is not enough to describe relationships that exist between classes. For example tow classes may exists, tree and flowering plant. A flowering tree is both a tree and flowering plant, a case can be made for deriving it from both of the base classes since it has the characteristics of both.

Multiple inheritance does not often prove necessary in an object oriented system, indeed many object oriented languages do not even support it. It allows information from more than one source to be mixed easily and extends the circumstances in which existing classes may be reused. However, complexity is increased and conflicts may arise between inherited attributes and methods.

If elements of the inherited bases classes overlap then ambiguity exists in the inheritance hierarchy. Ambiguity arises in the following situations:

  1. A derived class inherits two methods of the same name but different implementations from multiple base classes

  2. A derived class overrides a multiply defined inherited methods, but calls a base class method as part of its implementation.

  3. A derived class accesses an attribute or method inherited from a single ancestor by multiple base classes.

To overcome these ambiguities programmers must specify which method or attribute is required via the scope resolution. For example a developer may wish to extend common_function () defined in the base class CBaseClass1 and not that provided by CBaseClass2. This is achieved using the applying scope resolution to the function call as in CBaseClass1::common_function ().

Obviously multiple inheritance imposes some extra work on developers. When should this technique be used over others, such as aggregation? A number of factors should be considered when deciding on which technique to use

  1. What are the semantics of the objects - is the new object 'a kind of' more then one other object, or are the other objects 'a part of' our new object, or just other discrete objects with which the new object should communicate?

  2. Will aggregation be simpler to implement?

  3. What ambiguities need to be overcome?

  4. Is it necessary for our new object to be a derived class? Many object management mechanisms, such as containers, require a consistent set of interfaces for all the objects they send messages to. This consistency is achieved through base classes.

Questions

Create a small animal hierarchy ensuring the base classes can not be instantiated via pure virtual functions. The name of the animal, for example a robin, is an attribute of the base class set by its constructor. Ensure that constructor inheritance is used to set this variable.

Investigate how a thermostat connected to a heating system can be designed and implemented. You should consider aggregation, multiple inheritance and monolithic solutions. Your thermostat should be a simple device which will turn the heating on and off depending on temperature. It will consist of at least two underlying elements, a temperature gauge and a switch.

Downloads