DESTROYING DYNAMICALLY ALLOCATED OBJECTS ■ 549 Dynamically created objects in a class hierarchy are normally handled by a base class pointer. When such an object reaches the end of its lifetime, the memory occupied by the object must be released by a delete statement. Example: Car *carPtr; carPtr = new PassCar("500",false,21,"Geo"); . . . delete carPtr; ᮀ Destructor Calls When memory is released, the destructor for an object is automatically called. If multiple constructors were called to create the object, the corresponding destructors are called in reverse order. What does this mean for objects in derived classes? The destructor of the derived class is called first and then the destructor of the base class executed. If you use a base class pointer to manage an object, the appropriate virtual methods of the derived class are called. However, non-virtual methods will always execute the base class version. In the previous example, only the base class destructor for Car was executed. As the PassCar destructor is not called, neither is the destructor called for the data member passCarType, which is additionally defined in the derived class. The data member passCarType is a string, however, and occupies dynamically allocated memory— this memory will not be released. If multiple objects are created dynamically in the derived class, a dangerous situation occurs. More and more unreferenced memory blocks will clutter up the main memory without you being able to reallocate them—this can seriously impact your program’s response and even lead to external memory being swapped in. ᮀ Virtual Destructors This issue can be solved simply by declaring virtual destructors. The opposite page shows how you would define a virtual destructor for the Car class. Just like any other virtual method, the appropriate version of the destructor will be executed. The destructors from any direct or indirect base class then follow. A class used as a base class for other classes should always have a virtual destructor defined. Even if the base class does not need a destructor itself, it should at least contain a dummy destructor, that is, a destructor with an empty function body. 550 ■ CHAPTER 25 POLYMORPHISM VMT pointer Object of type Car Address of Car::display() Address of Car::~Car() VMT of Car nr producer 12345 Audi VMT pointer Address of Object of type Pass Car VMT of PassCar nr producer 54321 Geo passCarType PassCar::display() Address of PassCar::~PassCar() 500 sunRoof true VMT pointer Object of type Pass Car nr producer 98765 VW passCarType GTI sunRoof false ■ VIRTUAL METHOD TABLE VMT for the classes Car and PassCar VIRTUAL METHOD TABLE ■ 551 ᮀ Static Binding When a non-virtual method is called, the address of the function is known at time of compilation. The address is inserted directly into the machine code. This is also referred to as static or early binding. If a virtual method is called via an object’s name, the appropriate version of this method is also known at time of compilation. So this is also a case of early binding. ᮀ Dynamic Binding However, if a virtual method is called by a pointer or reference, the function that will be executed when the program is run is unknown at time of compilation. The statement Example: carPtr->display(); could execute different versions of the display() method, depending on the object currently referenced by the pointer. The compiler is therefore forced to create machine code that does not form an association with a particular function until the program is run. This is referred to as late or dynamic binding. ᮀ VMT Dynamic binding is supported internally by virtual method tables (or VMT for short). A VMT is created for each class with at least one virtual method—that is, an array with the addresses of the virtual methods in the current class. Each object in a polymorphic class contains a VMT pointer, that is, a hidden pointer to the VMT of the corresponding class. Dynamic binding causes the virtual function call to be executed in two steps: 1. The pointer to the VMT in the referenced object is read. 2. The address of the virtual method is read in the VMT. In comparison with static binding, dynamic binding does have the disadvantage that VMTs occupy memory. Moreover, program response can be impacted by indirect addressing of virtual methods. However, this is a small price to pay for the benefits. Dynamic binding allows you to enhance compiled source code without having access to the source code. This is particu- larly important when you consider commercial class libraries, from which a user can derive his or her own classes and virtual function versions. 552 ■ CHAPTER 25 POLYMORPHISM // cast_t.cpp // Dynamic casts in class hierarchies. // #include "car.h" bool inspect( PassCar* ), // Inspection of inspect(Truck* ); // car types. bool separate(Car* ); // Separates cars // for inspection. int main() { Car* carPtr = new PassCar("520i", true, 3265, "BMW"); Truck* truckPtr = new Truck(8, 7.5, 5437, "Volvo"); // to test some casts and separate(carPtr); separate(truckPtr); return 0; } bool separate( Car* carPtr) { PassCar* PassCarPtr = dynamic_cast<PassCar*>(carPtr); if( PassCarPtr != NULL) return inspect( PassCarPtr); Truck* truckPtr = dynamic_cast<Truck*>(carPtr); if( truckPtr != NULL) return inspect( truckPtr); return false; } bool inspect(PassCar* PassCarPtr) { cout << "\nI inspect a passenger car!" << endl; cout << "\nHere it is:"; PassCarPtr->display(); return true; } bool inspect(Truck* truckPtr) { cout << "\nI inspect a truck!" << endl; cout << "\nHere it is:"; truckPtr->display(); return true; } The compiler’s option “Run Time Type Information (RTTI)” must be activated, for example, under Project/Settings. The GNU compiler activates these options automatically. ✓ NOTE ■ DYNAMIC CASTS Using dynamic casts DYNAMIC CASTS ■ 553 ᮀ Safety Issues in Downcasting Downcasts in class hierarchies are unsafe if you use a C cast or the static cast operator. If the referenced object does not correspond to the type of the derived class, fatal runtime errors can occur. Given that carPtr is a pointer to the base class Car, which is currently pointing to a PassCar type, the statement Example: Truck * truckPtr = static_cast<Truck*>(carPtr); will not cause a compiler error. But the following statement, truckPtr->setAxles(10); could cause the program to crash. ᮀ The dynamic_cast<> Operator You can use the cast operator dynamic_cast<> to perform safe downcasting in poly- morphic classes. At runtime the operator checks whether the required conversion is valid or not. Syntax: dynamic_cast<type>(expression) If so, the expression expression is converted to the target type type. The target type must be a pointer or reference to a polymorphic class or a void pointer. If it is a pointer type, expression must also be a pointer type. If the target type is a reference, expression must identify an object in memory. ᮀ Examples Given a pointer carPtr to the base class Car, the statement Example: Truck* truckPtr = dynamic_cast<Truck*>(carPtr); performs a downcast to the derived Truck class, provided the pointer carPtr really identifies a Truck type object. If this is not so, the dynamic_cast<Truck> operator will return a NULL pointer. Given that cabrio is a PassCar type object, the following statements Example: Car& r_car = cabrio; PassCar& r_passCar=dynamic_cast<PassCar&>(r_car); perform a dynamic cast to the “reference to PassCar” type. In any other case, that is, if the reference r_car does not identify a PassCar type object, an exception of the bad_cast type is thrown. The dynamic cast can also be used for upcasting. The classes involved do not need to be polymorphic in this case. However, type checking is not performed at runtime. An erroneous upcast is recognized and reported by the compiler. exercises 554 ■ CHAPTER 25 POLYMORPHISM * * * Car Rental Management * * * P = Add a passenger car T = Add a truck D = Display all cars Q = Quit Your choice: bool insert(const string& tp, bool sr, long n, const string& prod); Add a new passenger car: bool insert(int a, double t, long n, const string& prod); Add a new truck: ■ EXERCISES Menu options Different versions of method insert() EXERCISES ■ 555 Exercise 1 Modify the vehicle management program to allow an automobile rental company to manage its fleet of automobiles. First, define a class called CityCar that contains an array of pointers to the 100 objects in the Car class.This also allows you to store pointers to objects of the derived class types PassCar and Truck. The objects themselves will be created dynamically at runtime. ■ Define a class CityCar with an array of pointers to the Car class and an int variable for the current number of elements in the array. The constructor will set the current number of array elements to 0. The destructor must release memory allocated dynamically for the remaining objects. Make sure that you use a virtual destructor definition in the base class Car to allow correct releasing of memory for trucks and passenger vehicles. Implement two versions of the insert() method using the prototype shown opposite. Each version will allocate memory to an object of the appropriate type—that is of the PassCar or Truck class—and use the arguments passed to it for initialization.The method should return false if it is impossible to enter another automobile (that is, if the array is full), and true in all other cases. The display() method outputs the data of all vehicles on screen.To perform this task it calls the existing display() method for each object. ■ Create a new function called menu() and store this function in a new source file.The function will display the menu shown opposite, read, and return the user’s choice. ■ Additionally, write two functions, getPassCar() and getTruck(), which read the data for a car or a truck from the keyboard and write the data into the appropriate arguments. ■ Create an object of the CityCar type in your main function. Insert one car and one truck.These will be the first vehicles of the company’s fleet. If a user chooses “Add car” or “Add truck,” your program must read the data supplied and call the appropriate version of insert(). 556 ■ CHAPTER 25 POLYMORPHISM Please type next article? 0 = No more articles 1 = Fresh food 2 = Prepacked article ? Another customer (y/n)? If yes to record Dialog with the receptionist In function record() Loop of main() EXERCISES ■ 557 Exercise 2 An automatic checkout system for a supermarket chain needs to be completed. ■ Declare the virtual methods scanner() and printer() in the base class Product.Also define a virtual destructor. ■ Write the record() function, which registers and lists products pur- chased in the store in a program loop. The function creates an array of 100 pointers to the base class, Product. The checkout assistant is prompted to state whether a prepacked or fresh food item is to be scanned next. Memory for each product scanned is allocated dynamically and referenced by the next pointer in the array. After scanning all the available items, a sequential list is displayed.The prices of all the items are added and the total is output at the end. ■ Now create an application program to simulate a supermarket checkout. The checkout assistant is prompted in a loop to state whether to define a new customer. If so, the record() function is called; if not, the program terminates. solutions 558 ■ CHAPTER 25 POLYMORPHISM ■ SOLUTIONS Exercise 1 // // car.h : Defines the base class Car and // the derived classes PassCar and Truck // #ifndef _CAR_H_ #define _CAR_H_ #include <iostream> #include <string> using namespace std; class Car { private: long nr; string producer; public: Car( long n = 0L, const string& prod = ""); virtual ~Car() {} // Virtual destructor. // Access methods: long getNr(void) const { return nr; } void setNr( long n ) { nr = n; } const string& getProd() const { return producer; } void setProd(const string& p){ producer = p; } virtual void display() const; // Display a car }; // The derived classes PassCar and Truck are unchanged // (see Chapter 23). #endif // // car.cpp // Implements the methods of Car, PassCar, and Truck // // Unchanged (see Chapter 23). // . vehicle management program to allow an automobile rental company to manage its fleet of automobiles. First, define a class called CityCar that contains an array of pointers to the 100 objects in the. pointer. Given that cabrio is a PassCar type object, the following statements Example: Car& r_car = cabrio; PassCar& r_passCar=dynamic_cast<PassCar&>(r_car); perform a dynamic cast to the. which read the data for a car or a truck from the keyboard and write the data into the appropriate arguments. ■ Create an object of the CityCar type in your main function. Insert one car and one