Inheritance, part 3

Pure Virtual Function
- Let’s look once again at our vehicle class hierarchy below

- We derive a Truck from a WheelVehicle derived from a Vehicle class

 

 

 

// Vehicle class

class Vehicle

{

protected:

  float distance;    // in miles

  float speed;       // in miles per hour

 

public:

  // Constructor

  Vehicle() { distance = 0.0; speed = 0.0; }

 

  // Compute time of travel

  virtual float computeDuration();

 

  // Access methods

  float getSpeed() { return speed; }

  void setSpeed(float s) { speed = s; }

  float getDistance() { return distance; }

  void setDistance(float d) { distance = d; }

};

float Vehicle::computeDuration()

{

  return ( distance / speed );

}

 

// Wheeled vehicle

class WheelVehicle : public Vehicle

{

protected:

  int wheels;        // number of wheels

 

public:

  // Constructor

  WheelVehicle() { wheels = 0; }

 

  // Compute duration

  float computeDuration()

  {

    float tmp = Vehicle::computeDuration();

    float road_construction = 1.0;

    return (tmp + road_construction);

  }

 

 

  // Access methods

  void setWheels(int w) { wheels = w; }

  int getWheels() { return wheels; }

};

 

// Truck class

class Truck : public WheelVehicle

{

private:

  float carryingLoad;

 

public:

  Truck() { carryingLoad = 0.0; }

 

  // Compute duration

  float computeDuration()

  {

    float tmp = WheelVehicle::computeDuration();

    return (tmp + carryingLoad);

  }

 

  // Access methods

  void setLoad( float l ) { carryingLoad = l; }

  float getLoad() { return carryingLoad; }

};

 

 

- Recall at the top of our hierarchy, we begin with a very general class

- This class contains information that is common to a wide variety of class types

- Note the virtual function (computeDuration) defined by each of the classes in the hierarchy

- Each class provides a description for this function consistent with its class type

- For the Vehicle class, we provide the following definition

 

return ( distance / speed );

 

- Even though the class is general, it makes sense to provide a description for this function

- With other functions, however, this might not be the case

- Consider, for example, a virtual function that describes how vehicles move

 

virtual void Move();

 

- It might be obvious to provide such definitions for the Truck or even the WheelVehicle class

- We would know how trucks and vehicles with wheels are to move

- On the other hand, what definition would we provide for the top-most Vehicle class?

- Remember that this class sits at the top of a hierarchy describing a wide variety of vehicles

- An expanded hierarchy might look something like this

 

 

- With this in mind, how would a general Vehicle class move?

- What definition would describe how both a Bike and a Plane moves?

- Even though both are types of vehicles, their move functions would be radically different

- It would be hard to find anything in common to include in a function to describe both types

- In fact, it might not make sense to put anything in this function for the Vehicle class

- This is the idea behind what is known as a pure virtual function

- Pure virtual functions do not contain a definition and are initialized to zero

 

virtual <return type> <function name> ( <args> ) = 0;

- Implementing our Move() function as a pure virtual function in the Vehicle class

class Vehicle
{
protected:
    ...

public:
    ...
    virtual void Move() = 0;
};
 

- Such methods are just placeholders (stubs) for derived classes to define

- They exist only to establish a common protocol to be used by all derived classes

 

 

 

 

Abstract Class

- Any class with at least one pure virtual function is known as an abstract class

- Abstract classes are used as base classes for derived classes
- Abstract classes are not to be used for creating (instantiating) objects

- In fact, it is illegal to create an object from an abstract class

- Instead, abstract classes are only to be used as pointers (base class pointers)
- You can use abstract class pointers to derived classes to leverage dynamic binding
- By providing their own definitions, derived classes override pure virtual functions in abstract classes

- If a derived class does not override all pure virtual methods of the base, the derived class becomes an abstract class as well

- Let’s look at an example of a simple hierarchy with an abstract class

 

#include <iostream>

 

using namespace std;

 

// Abstract base object class

class Object

{

protected:

  string name;

 

public:

  virtual void Draw() = 0;        // pure virtual functions

  virtual void Print() = 0;

};

 

class Sphere : public Object

{

protected:

  float radius;

 

public:

  Sphere(float r) { radius = r; }

 

  // Override pure virtual functions in base

  void Draw();

  void Print();

};

void Sphere::Draw()

{

  // draw sphere object, overrides pure virtual

  cout << "Sphere::Draw()" << endl;

}

void Sphere::Print()

{

  // print sphere info, overrides pure virtual

  cout << "Sphere::Print()" << endl;

}

class Cube : public Object

{

protected:

  float size;

 

public:

  Cube(float s) { size = s; }

 

  // Override pure virtual functions in base

  void Draw();

  void Print();

};

void Cube::Draw()

{

  // draw cube object, overrides pure virtual

  cout << "Cube::Draw()" << endl;

}

void Cube::Print()

{

  // print cube info, overrides pure virtual

  cout << "Cube::Print()" << endl;

}

 

#include <vector>

 

main()

{

  // Array of abstract base class pointers

  vector<Object *> shapes;

 

  // Store derived types of objects in base pointers

  shapes.push_back( new Sphere(2.0) );

  shapes.push_back( new Cube(1.0) );

 

  // Process objects

  for(int i = 0; i < shapes.size(); ++i )     {

      shapes[i]->Draw();

      shapes[i]->Print();

    }

}

Output
Sphere::Draw()
Sphere::Print()
Cube::Draw()
Cube::Print()

 

- Note the pure virtual functions in the base abstract class

- Note how the derived classes provide definitions to override the abstract methods

- Note how abstract class pointers and dynamic binding provide a generic method of programming

- We are able to easily manage collections of different types of objects using such pointers