160 likes | 302 Views
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.
E N D
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 Train Truck
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; } fleet[0] is of type Vehicle! Output: Vehicle Vehicle
Inheritance & virtual functions Vehicle *fleet[2]; fleet[0] = new Train; fleet[1] = new Truck; • 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 hierarchy, take 2 class 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 dynamic types of *fleet[0] and *fleet[1] are determined at runtime. The appropriate method is called based on the dynamic type of fleet[i]. For input 1 2 the output is: Train Truck
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 calls in this program is: Vehicle() Train() ~Vehicle() The Train destructor was never called, because it's not virtual! The Vehicle destructor was called because this is the type of *t.
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()
Assignments class Base { ... }; class Derived : public Base {...}; Base b, *pb; Derived der, *pder; • 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.
Assignments class Base { ... }; class Derived : public Base {...}; Base b, *pb; Derived der, *pder; • 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)
Assignments class Base { ... }; class Derived : public Base {...}; Base b, *pb; Derived der, *pder; • 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.
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 dynamic type 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.
Downcasting 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++) { Train *tr = dynamic_cast<Train *> ( fleet[i] ); if (tr != NULL) // it means that fleet[i] was indeed a train // do A, else // do B. ...
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{ ... };
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 { ... };
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 { ... };
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!
Abstract classes 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”;} }; • 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!