Thursday, October 12, 2006
Inheritance - Polymorphism

Topics

Example so far:

// Vehicle class
class Vehicle
{
private:
    float distance;    // in miles
    float speed;       // in miles per hour

public:

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

    // Rate = distance/speed
    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 (derived from Vehicle class)
class WheelVehicle : public Vehicle
{
private:
    int wheels;        // number of wheels

public:

    // Constructor
    WheelVehicle() { wheels = 0; }

    // Access methods
    void setWheels(int w) { wheels = w; }
    int getWheels() { return wheels; }
};

 

// Truck class (derived from WheelVehicle class)
class Truck : public WheelVehicle
{
private:
    float carryingLoad;

public:
    Truck() { carryingLoad = 0.0; }

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

// Application
#include <stdio.h>

main()
{
    WheelVehicle wheely;
    wheely.setWheels(4);
    wheely.setSpeed(65.0);
    wheely.setDistance(300.0);
    printf( "Duration for wheely: %f hrs\n", wheelv.computeDuration() );
 

    Truck semi;
    semi.setLoad(2);
    semi.setWheels(18);
    semi.setSpeed(55.0);
    semi.setDistance(300.0);
    printf( "Duration for truck: %f hrs\n", semi.computeDuration() );
}
 

Output
Duration for wheely: 4.615385 hours
Duration for truck: 5.454545 hours


 

Base and Derived relationships
 

- Derived class can be assigned to any of its public base classes without explicit casting
- Sets up a "generic" style of programming where the actual type of a class is unknown
 

Example 1

#include <stdio.h>

main()
{
    Truck semi;
    semi.setLoad(2);
    semi.setWheels(18);
    semi.setSpeed(55.0);
    semi.setDistance(300.0);
    printf( "Duration for truck: %f hrs\n", semi.computeDuration() );
 

  // Derived class assigned to base class
    WheelVehicle &wv = semi;
    printf( "Duration for wv: %f hrs\n", wv.computeDuration() );
 

  // Derived class assigned to parent of base class
    Vehicle &v = semi;
    printf( "Duration for v: %f hrs\n", v.computeDuration() );
 

  // Derived class address assigned to base class pointer
    Vehicle *vp = &semi;
    printf( "Duration for vp: %f hrs\n", vp->computeDuration() );
}
 

Output
Duration for truck: 5.454545 hrs
Duration for wv: 5.454545 hrs
Duration for v: 5.454545 hrs
Duration for vp: 5.454545 hrs
 
 

Example 2

#include <stdio.h>

void printDuration( Vehicle *v );

main()
{
    Truck semi;
    semi.setLoad(2);
    semi.setWheels(18);
    semi.setSpeed(55.0);
    semi.setDistance(300.0);
    printf( "Duration for truck: %f hrs\n", semi.computeDuration() );

  // Can pass address of derived type
    printDuration( &semi );

          // Can pass address of derived type
    Vehicle *vp = &semi;
    printDuration( vp );
}

void printDuration( Vehicle *v )
{
    printf( "Duration: %f hrs\n",
             v->computeDuration() );

}
 

Output
Duration for truck: 5.454545 hrs
Duration: 5.454545 hrs
Duration: 5.454545 hrs
 

- Which computeDuration method (in which class) is being called above?
- We might want to have different meanings for computeDuration in derived classes
- If we could, how will our printDuration function know the difference?
- How can we distinguish between the different derived class types?
- A first attempt (in a nonobject-oriented manner) might look something like this...
 

Pseudocode example

#define VEHICLE 0
#define WHEELV 1
#define TRUCK 2

add
    int whatIsMyType() { return (VEHICLE, WHEELV, TRUCK) }
to every class

void printDuration( Vehicle *v )
{
    switch( v->whatIsMyType() )
    {
        case VEHICLE:
            ....
        break;

        case WHEELV:
            ....
        break;

        case TRUCK:
            ....
        break;
    }
}


- Note that the burden of "type resolution" rests with the programmer
- Would be nice if we could shift this burden to the compiler....
 
 

Dynamic (late) binding
- Class type determined at run-time by compiler
- Compiler cannot determine class type at compile time
- Used with pointers to objects only

Vehicle *v = new Truck;

- Compiler doesn't know which object type is being referenced
- Which class is it?  Vehicle?  WheelVehicle?  Truck?


- No need for programmer to determine type in example above
- Reduces size and complexity of code
- Makes code more extensible (further derived classes follow suit)
- Opposite of static (early) binding (compiler knows at compile time)
- What type of binding is used with overloaded functions?
- To implement dynamic binding, we need a new mechanism...
 
 
 

Virtual Functions
- Mechanism for modifying inherited functions
- Different methods for different derived classes
- Derived class overrides base class function and provides its own version
- A better term would be "adaptable" rather than virtual
- Overridden method in derived class is adapted to fit new situation
- Functions are declared to be virtual under the following conditions
        1) A class expects to be an object of derivation
        2) The implementation of the function is type-dependent

- Objects respond to same commands in different ways
- This feature is also called polymorphism
- Where else have seen an example in C++ of polymorphism?
 

Virtual Function syntax
- Precede function declaration with keyword virtual
- Keyword need only be denoted in base class, not necessary in derived class(es)

virtual <return type> <name> (<arg1, arg2, ...>)


Rules when declaring virtual functions properly:
   1) The virtual keyword must be used in the base class
    2) Both base and derived class methods must have same parameters and types
        (Their function declaration must be equivalent)
 

Example
- Consider a general base class describing an error message
- Create a more specific type of error message with derived class
 

// General error message
class error_msg
{
public:
    virtual void display()
    {
        printf( "An error has occurred\n" );
    }
};
 

// Specific type of syntax error message
class syntax_msg : public error_msg
{
public:
    void display()
    {
        printf( "Semicolon missing in statement\n" );
    }
};
 

#include <stdio.h>

main()
{
    error_msg *m[2];            // array of object pointers
    error_msg msg;              // error message
    syntax_msg smsg;            // syntax error message

    m[0] = &msg;
    m[1] = &smsg;

    m[0]->display();
    m[1]->display();
}
 

Output
An error has occurred
Semicolon missing in statement

Virtual functions and overloaded functions
- Note how at run-time, the program determined the class type of the pointers
- This feature is known as dynamic binding (type determined at run-time)
- Why bother with virtual functions, why not just override the function?
- Let's see why....

class error_msg
{
public:

  // No longer virtual!
    void display()
    {
        printf( "An error has occurred\n" );
    }
};

class syntax_msg : public error_msg
{
public:

  // Overrides function in base class
    void display()
    {
        printf( "Semicolon missing in statement\n" );
    }
};

#include <stdio.h>

main()
{
    error_msg *m[2];
    error_msg msg;
    syntax_msg smsg;

    m[0] = &msg;
    m[1] = &smsg;

    m[0]->display();
    m[1]->display();
}

Output
An error has occurred
An error has occurred

- When using base class pointers to derived objects, if the virtual keyword is not used...
        1) and the base and derived methods are similar in name and parameters
                the base method overrides the derived method

        2) and the base and derived methods are similar in name, different in parameters
                the derived method overloads the base method
 
 

Virtual functions and pointers
- Consider the following change to our example

#include <stdio.h>

main()
{
    error_msg m[2];
    error_msg msg;
    syntax_msg smsg;

    m[0] = msg;
    m[1] = smsg;

    m[0].display();
    m[1].display();
}
 

Output
An error has occurred
An error has occurred


- What happened?
- What have we changed?
- Remember dynamic binding works with pointers!


 

Final Example
- Let's return to our original example using vehicle types
 

// General vehicle class
class Vehicle
{
private:
    float distance;       // in miles
    float speed;          // in miles per hours
    char name[100];       // added name of vehicle

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

  // Virtual function (type dependent)
    virtual float computeDuration();
 

    // Access methods
    void setName(char *s) { strcpy(name,s); }
    char *getName() { return name; }
    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 );
}
 
 

// Specific type of vehicle w/ wheels
class WheelVehicle : public Vehicle
{
private:
    int wheels;

public:
    WheelVehicle() { wheels = 0; }

    // Derived virtual method.
    //   Even if you don't include the
    //   keyword virtual, which is optional,
    //   the function is still virtual
    //   because it is virtual in the base
    //   class. 
 
    virtual float computeDuration();

    // Access methods
    void setWheels(int w) { wheels = w; }
    int getWheels() { return wheels; }
};

float WheelVehicle::computeDuration();
{
    float tmp = getDistance()/getSpeed();
    float road_construction = 0.1;

    return (tmp + tmp*road_construction);
}
 

// Specific wheeled vehicle type
class Truck : public WheelVehicle
{
private:
    float carryingLoad;   // in tons

public:
    Truck() { carryingLoad = 0.0; }

    // Derived virtual function
    float computeDuration();

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

float Truck::computeDuration();
{
    float tmp = getDistance()/getSpeed();
    float road_construction = 0.1;

    return (tmp + tmp*road_construction + tmp*carryingLoad*0.1);
 }
 

#include <stdio.h>
#include <string.h>

main()
{
    // Instantiate a car
    WheelVehicle car;
    car.setName("car");
    car.setWheels(4);
    car.setSpeed(65.0);

    // Instantiate a semi truck
    Truck semi;
    semi.setName("semi");
    semi.setLoad(2);
    semi.setWheels(18);
    semi.setSpeed(55.0);

    // Instantiate a pickup truck
    Truck pickup;
    pickup.setName("pickup");
    pickup.setLoad(0.2);
    pickup.setWheels(4);
    pickup.setSpeed(70.0);

    // Create a fleet for a company
    Vehicle *fleet[3];
    fleet[0] = &car;
    fleet[1] = &semi;
    fleet[2] = &pickup;
 

    // Compute duration of trip for different vehicle types
    for( int i = 0; i < 3; ++i )
    {
        // Set a distance
        fleet[i]->setDistance(300.0);

        // Print the duration of the trip
        printf("Fleet %d - %s, Duration: %.2f\n", i,
                fleet[i]->getName(), fleet[i]->computeDuration() );
    }
}

Output
Fleet 0 - car, Duration: 4.15
Fleet 1 - semi, Duration: 3.82
Fleet 2 - pickup, Duration: 3.77


- Note how different methods are called for different types
- Note how dynamic binding occurs at run-time
- Note how this is implemented with virtual functions and object pointers
 
 

Readings

Deitel & Deitel Fourth Edition: 9.4 - 9.7, 10.1 - 10.7

 

Back to Csc 125 - Computer Science II, Programming in C++
  Scott Badman   Office: B132   Phone: 353-2250   sbadman@parkland.edu  

Parkland College, 2400 W. Bradley Avenue, Champaign, IL 61821