1 Inheritance We are modeling the operation of a transportation company that uses trains and trucks...

16
1 Inheritance We are modeling the operation of a transportation company that uses trains and trucks to transfer goods. A suitable class hierarchy for the vehicles is: We will store all the vehicle information in array. Create an array of n vehicle objects. Some will be trains, and the rest will be trucks. How do we do it? Vehicle Truck Train

Transcript of 1 Inheritance We are modeling the operation of a transportation company that uses trains and trucks...

1

Inheritance

We are modeling the operation of a transportation company that uses trains and trucks to transfer goods. A suitable class hierarchy for the vehicles is:

We will store all the vehicle information in array. Create an array of n vehicle objects. Some will be trains,

and the rest will be trucks. How do we do it?

Vehicle

Truck Train

2

Vehicle hierarchy, take 1

class Vehicle {public: Vehicle() { } void print() { cout << “Vehicle\n”;}};class Train : public Vehicle {public: Train() { } void print() { cout << “Train\n”;}};class Truck : public Vehicle {public: Truck() { } void print() { cout << “Truck\n”;}};

int main () { Vehicle *fleet[2];

fleet[0] = new Train; fleet[0]->print();

fleet[1] = new Truck; fleet[1]->print();

delete fleet[0]; delete fleet[1]; return 0;}

Output:VehicleVehicle

fleet[0]is of typeVehicle!

3

Inheritance & virtual functions

A Train is a Vehicle, so the syntax fleet[0] = new Train; works, but ultimately, *fleet[0] is a Vehicle object, so we cannot apply any Train methods to it.

We would like our program to have the capability to determine the type of an object (e.g. *fleet[0]) at execution time. There is such a mechanism and it's called RTTI

(Run-Time Type Information) We will use virtual functions which allow the program to

choose the derived class function, dynamically, instead of the base class function.

Vehicle *fleet[2];

fleet[0] = new Train; fleet[1] = new Truck;

4

Vehicle hierarchy, take 2class Vehicle {public: Vehicle() { } virtual void print() { cout << “Vehicle\n”; }};class Train : public Vehicle {public: Train() { } virtual void print() { cout << “Train\n”; }};class Truck : public Vehicle {public: Truck() { } virtual void print() { cout << “Truck\n”; }};

int main () { Vehicle *fleet[2]; int vehicle_type;

for (int i=0; i<2; i++) { cout << "Enter 1 for Train,2 for Truck"; cin >> vehicle_type; if (vehicle_type == 1) fleet[i] = new Train; else fleet[i] = new Truck;

fleet[0]->print(); fleet[1]->print();

delete fleet[0]; delete fleet[1]; return 0;}

The appropriate methodis called based on the dynamic type of fleet[i].

The dynamic types of *fleet[0] and *fleet[1] are determined at runtime.

For input 1 2 the output is:TrainTruck

5

Virtual functions

If a class has virtual methods, then the destructor must also be virtual!

class Vehicle {public: Vehicle() { } ~Vehicle { } virtual void print() {};};

class Train : public Vehicle {public: Train() { } ~Train { } virtual void print() {};} ;

int main () { Vehicle *t = new Train; delete t; return 0;}

The sequence of constructor/destructor callsin this program is:

Vehicle()Train()

~Vehicle()

The Train destructor was never called, becauseit's not virtual! The Vehicle destructor was called becausethis is the type of *t.

6

Virtual functions

If a class has virtual methods, then the destructor must also be virtual!

class Vehicle {public: Vehicle() { } virtual ~Vehicle { } virtual void print() {};};

class Train : public Vehicle {public: Train() { } virtual ~Train { } virtual void print() {};} ;

int main () { Vehicle *t = new Train; delete t; return 0;}

Now, the sequence of constructor/destructor calls is correct:

Vehicle()Train()~Train()~Vehicle()

7

Assignments

der = b; // ERROR! A Base object is not always a Derived object. // Note: what values will be given to the "extra" data // members of the Derived object?

pder = pb // ERROR! Same reason as above.

class Base { ... };

class Derived : public Base {...};

Base b, *pb;

Derived der, *pder;

8

Assignments

b = der; // OK! A Derived object is also a Base object pb = pder; // Mostly OK!

pb->basefunc() will work (basefunc() is a Base member function)

pb->derivedfunc() will give a compiler ERROR (derivedfunc() is a Derived member function)

class Base { ... };

class Derived : public Base {...};

Base b, *pb;

Derived der, *pder;

9

Assignments

pb = new Derived; pder = pb ; // ERROR!

pb = new Derived;pder = (Derived*) pb ; // Dangerous! We are trying to downcast! However, there is a case when we may have to downcast.

class Base { ... };

class Derived : public Base {...};

Base b, *pb;

Derived der, *pder;

10

Downcasting

We'd like to do this:

int main () { Vehicle *fleet[2]; int vehicle_type;

for (int i=0; i<2; i++) { cout << "Enter 1 for Train,2 for Truck"; cin >> vehicle_type; if (vehicle_type == 1) fleet[0] = new Train; else fleet[1] = new Truck;

for (int i=0; i<2; i++) { if *fleet[i] is a train, do A else, do B } ...

How can we check the dynamictype of *fleet[i] at runtime?

Trick: • Create a Train pointer and assign fleet[i] to it. Use the dynamic_cast method to perform this downcasting (from Vehicle* to Train*)• If fleet[i]'s dynamic type is also Train*, then the casting will succeed.• If it's not (i.e. it's a Truck* instead), then the casting fails, and the pointer is NULL.

11

Downcastingint main () { Vehicle *fleet[2]; int vehicle_type;

for (int i=0; i<2; i++) { cout << "Enter 1 for Train,2 for Truck"; cin >> vehicle_type; if (vehicle_type == 1) fleet[0] = new Train; else fleet[1] = new Truck;

for (int i=0; i<2; i++) { Train *tr = dynamic_cast<Train *> ( fleet[i] ); if (tr != NULL) // it means that fleet[i] was indeed a train // do A, else // do B. ...

12

Multiple inheritance

A class may inherit from more than one base class.

JetCar inherits the members of Car and those of Jet.

JetCar's constructor calls the constructors of Car and Jetin the order specified in JetCar's definition (i.e. as they appear after the colon)

class Car { ... };

class Jet { ... };

class JetCar: public Car, public Jet{ ... };

13

Multiple inheritance

What if we have:

JetCar's constructor calls the constructor of Car

which calls the constructor of Vehicle and then calls the constructor of Jet

which calls the constructor of Vehicle The Vehicle constructor was called twice!

class Vehicle { ... };

class Car : public Vehicle { ... };

class Jet : public Vehicle { ... };

class JetCar: public Car, public Jet { ... };

14

Multiple inheritance

Use virtual inheritance to tell the compiler that the base class constructor should be called only once:

JetCar's constructor calls the constructor of Car

which calls the constructor of Vehicle and then calls the constructor of Jet

class Vehicle { ... };

class Car : virtual public Vehicle { ... };

class Jet : virtual public Vehicle { ... };

class JetCar: public Car, public Jet { ... };

15

Abstract classes

Since the company has either trucks or trains, we would like to disallow the creation of Vehicle objects.

We want to force the compiler to give an error in the following case:

In other words, we want to make Vehicle an abstract class. This will make Train and Truck concrete classes.

To make a class as abstract, we declare one of its virtual methods to be "pure", by placing a =0 at the end of its declaration.

Vehicle *v;

v = new Vehicle; // ERROR: We should only create // a train or a truck!

16

Abstract classes

Pure virtual functions typically do not have an implementation.

Every concrete derived class MUST override all base class pure virtual functions.

Even though we can't instantiate abstract class objects, we can still use the abstract class to create pointers. Vehicle *v; // OK Vehicle *v = new Train; // OK Vehcile *v = new Vehicle //

ERROR!

class Vehicle {public: Vehicle() { } virtual void print() = 0;};

class Train : public Vehicle {public: Train() { } void print() { cout << “Train\n”;}};

class Truck : public Vehicle {public: Truck() { } void print() { cout << “Truck\n”;}};