Lecture 4 TCP1201 OOPDS 1
Learning Objectives To understand static polymorphism and dynamic polymorphism To understand the problem of static polymorphism To enable dynamic polymorphism via virtual function To understand the benefit of dynamic polymorphism To understand abstract class To understand why virtual destructor is needed 2
What is Polymorphism? -Polymorphism is allowing objects of different types to respond differently to the same method call. -Polymorphism occurs when there is function overriding. - In lecture 3, superclass Human has a speak method and is overridden by subclass Student. Invoking the speak method from Human has different result than Invoking the speak method from Student. 3
Different objects invoke different version of methods with the same name (speak()) class Animal { public: void speak() { How does C++ knows cout << "I'm an animaln"; which method to call? } Through function call }; binding class Bird : public Animal { public: void speak() { // overriding cout << "I'm a birdn"; } Output: }; I'm an animal I'm a bird int main() { Animal* a = new Animal; a->speak(); // call Animal::speak() Bird* b = new Bird; b->speak(); // call Bird::speak() delete a; delete b; 4 }
Function Call Binding • Connecting a function call to a function body is called binding • Function call binding is the process of determining what block of function code is executed when a function call is made. class Animal { Memory public: void speak() { ... } }; Animal::speak() 0x7723 class Bird : public Animal { Definition public: void speak() { ... } }; Bird::speak() 0x77b4 int main() { Definition Animal* a = new Animal; a->speak(); Bird* b = new Bird; b->speak(); delete a; delete b; 5 }
Function Call Binding There are 2 types of function call binding (polymorphism): Early/Static/Compile-time binding/polymorphism: Binding is performed during compile-time, and is decided by the compiler and linker based on the variable used to invoke the method. Late/Dynamic/Runtime binding/polymorphism: Binding occurs at runtime, based on the type of the object calling the method. 6
Static Polymorphism • It is the polymorphism that is implemented using static binding. • The compiler determines beforehand what method definition will be executed for a particular method call, by looking at the type of the variable invoking the method. • We have been using static polymorphism thus far without realizing it. 7
Static Polymorphism Consider the following Animal hierarchy, subclass Bird overrides superclass Animal's move() method. class Animal { Animal public: void move() { cout << "Moving"; } + move():void }; class Bird : public Animal { public: void move() { cout << "Flying"; } Bird }; + move():void 8
Static Polymorphism Call the move() method from an Animal object, an Animal pointer/reference to an Animal object, a Bird object, or an Bird pointer/reference to a Bird object, got overriding but no upcasting => no problem int main() { Animal a1; a1.move(); // cout "Moving". Animal *a2 = new Animal; // Pointer. a2->move(); // cout "Moving". Animal& a3 = a1; // Reference. Animal::move() is called. a3.move(); // cout "Moving". No problem Bird b1; b1.move(); // cout "Flying". Bird *b2 = new Bird; // Pointer. b2->move(); // cout "Flying". Bird& b3 = b1; Bird::move() is called. No b3.move(); // cout "Flying". problem 9 }
Static Polymorphism: Problem Call the move() method from a Bird object using an Animal pointer/reference, got overriding and got upcasting => big problem. int main() { Bird* b1 = new Bird; Animal* a1 = b1; // Upcasting via pointer. a1->move(); // cout "Moving". Bird b2; a1 = &b2; // Upcasting via pointer. a1->move(); // cout "Moving". Animal& a2 = b2; // Upcasting via reference. a2.move(); // cout "Moving". delete b1; } Animal::move() is still called but Bird::move() is more appropriate/precise/accurate in the context. 10
Static Polymorphism: Problem Consider the following inheritance hierarchy: class Animal { public: void move() { cout << "Moving"; } }; class Bird : public Animal { public: void move() { cout << "Flying"; } }; class Fish : public Animal { public: void move() { cout << "Swimming"; } }; class Mammal : public Animal { public: void move() { cout << "Walking"; } }; 11
Static Polymorphism: Problem 1 To make a function callMove() to support every class in the animal inheritance hierarchy, we have to write/overload the callMove() function for every class. // 1 callMove() only. void callMove (Animal & a) Output: int main() { { ... Animal a; Moving a.move(); Bird b; } Moving Fish f; Moving // 4 callMove() for Mammal m; Moving // 4 different classes. callMove (a); void callMove (Animal & a) callMove (b); { ... a.move(); } callMove (f); Output: void callMove (Bird & b) callMove (m); { ... b.move(); } } Moving void callMove (Fish & f) Flying { ... f.move(); } Swimming void callMove (Mammal & m) Walking 12 { ... m.move(); }
Static Polymorphism: Problem 1 We can actually solve the problem in previous slides by using template. However the downside is the template function won't be limited to the Animal hierarchy anymore. // template callMove(). // Work but not limited to Animal hierarchy. template <typename T> void callMove (T & a) { ... a.move(); } 13
Static Polymorphism: Problem 2 We cannot use one for loop to call the correct version of move() method in an array/vector of objects from Animal hierarchy. Current output: int main() { Moving // Array of different animals. Moving Animal* a[4] = {new Animal, Moving new Bird, Moving new Fish, new Mammal}; Preferred output: for (int i = 0 ; i < 4; i++) a[i]->move(); Moving for (int i = 0 ; i < 4; i++) Flying delete a[i]; Swimming } Walking 14
Dynamic Polymorphism • The solution to the upcasting problem we just discussed is to enable dynamic polymorphism or dynamic binding via the use of virtual function. • Requirements for C++ dynamic polymorphism: • There must be an inheritance hierarchy. • The superclass must have a virtual method. • Subclass must override superclass virtual method. • A superclass pointer/reference to a subclass object (upcasting) is used to invoke the virtual method. 15
Virtual Function • In order to allow a method to be bound at run-time, it must be declared as virtual in the superclass. • By declaring a method as virtual in superclass, it is automatically virtual in all subclasses. class Animal { public: virtual void move() { // Enable dynamic binding. cout << "Moving"; } }; class Bird : public Animal { public: void move() { // Automatically virtual. cout << "Flying"; } }; 16
Virtual Function  We can use the keyword virtual at subclass' virtual function to remind ourselves that it is using dynamic binding. class Animal { public: virtual void move() { cout << "Moving"; } }; class Bird : public Animal { public: virtual void move() { // Optional, as reminder. cout << "Flying"; } }; 17
Dynamic Polymorphism After making the move() method virtual: int main() { Bird* b1 = new Bird; Animal* a1 = b1; // Upcasting via pointer. a1->move(); // cout "Flying". Bird b2; a1 = &b2; // Upcasting via pointer. a1->move(); // cout "Flying". Animal& a2 = b2; // Upcasting via reference. a2.move(); // cout "Flying". delete b1; } Bird::move() is called now, which is more appropriate/precise/accurate in the context. 18
Dynamic Polymorphism: Benefit The primary benefit of dynamic polymorphism is it enables to write codes that work for all objects from the same inheritance hierarchy via upcasted pointer / reference. Consider the following inheritance hierarchy: class Animal { public: virtual void move() { cout << "Moving"; } }; class Bird : public Animal { public: virtual void move() { cout << "Flying"; } }; class Fish : public Animal { public: virtual void move() { cout << "Swimming"; } }; class Mammal : public Animal { public: virtual void move() { cout << "Walking"; } }; 19
Dynamic Polymorphism: Benefit We can write a single callMove() function that call the correct version of move() method for all objects in the Animal hierarchy. void callMove (Animal & a) { ... a.move(); } Output: int main() { Animal a; Moving Bird b; Flying Fish f; Swimming Mammal m; Walking callMove (a); callMove (b); callMove (f); callMove (m); 20 }
Dynamic Polymorphism: Benefit We can also write a loop to call the correct version of move() method for all objects in the Animal hierarchy. int main() { // Array of different animals. Output: Animal* a[4] = {new Animal, new Bird, Moving new Fish, Flying new Mammal}; Swimming for (int i = 0 ; i < 4; i++) Walking a[i]->move(); for (int i = 0 ; i < 4; i++) delete a[i]; } 21
Abstract Class  An abstract class is a class that cannot be instantiated.  A class that can be instantiated is called a concrete class. – All classes that we have learned so far are concrete classes.  The reason to make a class abstract is, due to some requirements/constraints, we want to make it impossible to create instance(s) of the class.  To be an abstract class: a class must have at least a pure virtual function. 22
Pure Virtual Function  A pure virtual function is a method that is initialized to zero (0) in its declaration and no implementation is given in the class.  Pure virtual function makes the class abstract and no instance of the class can be created. class Animal { // Abstract class. public: virtual void move() = 0; // Pure virtual function. }; int main() { Animal a1; // ERROR: Animal is an abstract class. Animal* a2 = new Animal; // ERROR: same reason. ... } 23
Pure Virtual Function  Subclass of an abstract class must override all of the superclass pure virtual functions in order to become a concrete class (instantiate-able). class Animal { // Abstract superclass. public: virtual void move() = 0; // Pure virtual function. }; class Bird : public Animal { // Concrete subclass. public: virtual void move() { // Overriding pure virtual function. cout << "Flying"; } }; int main() { Animal *a = new Bird; // Fine. create a Bird object. a->move(); // cout "Flying“. } 24
Pure Virtual Function  Subclass that does not override superclass' pure virtual function is an abstract class too. class Animal { // Abstract class. public: virtual greet() {} virtual void move() = 0; // Pure virtual function. }; class Bird : public Animal { // Abstract class. public: virtual void greet() { ... } }; class Eagle : public Bird { // Concrete class. public: virtual void greet() { ... } virtual void move() { ... } // Overriding. }; 25
Virtual Destructor  Superclass destructor should always be virtual in order for the delete operator to call the destructors correctly for an subclass object that is created via upcasting.  Otherwise, the delete operator will call only superclass destructor, not the subclass destructor. – This will cause only the base part of the object to be destroyed. // Problem int main() { class Super { Super* p = new Sub; // Upcasting. public: delete p; // Invoke destructor. ~Super() { // Non virtual. } cout << "Super destroyedn"; } }; class Sub : public Super { Output: // Where is Sub's destructor? public: Super destroyed ~Sub() { cout << "Sub destroyedn"; } 26 };
Virtual Destructor // Solution  To ensure that class Super { subclass objects are public: destroyed properly, virtual ~Super() { make superclass cout << "Super destroyedn"; } destructor virtual. }; class Sub : public Super { public: virtual ~Sub() { Output: cout << "Sub destroyedn"; } Sub destroyed }; Super destroyed int main() { Super* p = new Sub; // Upcasting. delete p; // Invoke destructor. } 27
Good Programming Practices  Use inheritance to promote code reusability.  Use virtual function to describe common behavior within a family of classes.  Use pure virtual function to force subclasses to define the virtual function.  Use a pointer/reference to an abstract base class to invoke virtual function, thus implementing dynamic polymorphism. 28

Lecture04 polymorphism

  • 1.
  • 2.
    Learning Objectives To understandstatic polymorphism and dynamic polymorphism To understand the problem of static polymorphism To enable dynamic polymorphism via virtual function To understand the benefit of dynamic polymorphism To understand abstract class To understand why virtual destructor is needed 2
  • 3.
    What is Polymorphism? -Polymorphismis allowing objects of different types to respond differently to the same method call. -Polymorphism occurs when there is function overriding. - In lecture 3, superclass Human has a speak method and is overridden by subclass Student. Invoking the speak method from Human has different result than Invoking the speak method from Student. 3
  • 4.
    Different objects invokedifferent version of methods with the same name (speak()) class Animal { public: void speak() { How does C++ knows cout << "I'm an animaln"; which method to call? } Through function call }; binding class Bird : public Animal { public: void speak() { // overriding cout << "I'm a birdn"; } Output: }; I'm an animal I'm a bird int main() { Animal* a = new Animal; a->speak(); // call Animal::speak() Bird* b = new Bird; b->speak(); // call Bird::speak() delete a; delete b; 4 }
  • 5.
    Function Call Binding •Connecting a function call to a function body is called binding • Function call binding is the process of determining what block of function code is executed when a function call is made. class Animal { Memory public: void speak() { ... } }; Animal::speak() 0x7723 class Bird : public Animal { Definition public: void speak() { ... } }; Bird::speak() 0x77b4 int main() { Definition Animal* a = new Animal; a->speak(); Bird* b = new Bird; b->speak(); delete a; delete b; 5 }
  • 6.
    Function Call Binding Thereare 2 types of function call binding (polymorphism): Early/Static/Compile-time binding/polymorphism: Binding is performed during compile-time, and is decided by the compiler and linker based on the variable used to invoke the method. Late/Dynamic/Runtime binding/polymorphism: Binding occurs at runtime, based on the type of the object calling the method. 6
  • 7.
    Static Polymorphism • Itis the polymorphism that is implemented using static binding. • The compiler determines beforehand what method definition will be executed for a particular method call, by looking at the type of the variable invoking the method. • We have been using static polymorphism thus far without realizing it. 7
  • 8.
    Static Polymorphism Consider thefollowing Animal hierarchy, subclass Bird overrides superclass Animal's move() method. class Animal { Animal public: void move() { cout << "Moving"; } + move():void }; class Bird : public Animal { public: void move() { cout << "Flying"; } Bird }; + move():void 8
  • 9.
    Static Polymorphism Call themove() method from an Animal object, an Animal pointer/reference to an Animal object, a Bird object, or an Bird pointer/reference to a Bird object, got overriding but no upcasting => no problem int main() { Animal a1; a1.move(); // cout "Moving". Animal *a2 = new Animal; // Pointer. a2->move(); // cout "Moving". Animal& a3 = a1; // Reference. Animal::move() is called. a3.move(); // cout "Moving". No problem Bird b1; b1.move(); // cout "Flying". Bird *b2 = new Bird; // Pointer. b2->move(); // cout "Flying". Bird& b3 = b1; Bird::move() is called. No b3.move(); // cout "Flying". problem 9 }
  • 10.
    Static Polymorphism: Problem Callthe move() method from a Bird object using an Animal pointer/reference, got overriding and got upcasting => big problem. int main() { Bird* b1 = new Bird; Animal* a1 = b1; // Upcasting via pointer. a1->move(); // cout "Moving". Bird b2; a1 = &b2; // Upcasting via pointer. a1->move(); // cout "Moving". Animal& a2 = b2; // Upcasting via reference. a2.move(); // cout "Moving". delete b1; } Animal::move() is still called but Bird::move() is more appropriate/precise/accurate in the context. 10
  • 11.
    Static Polymorphism: Problem Considerthe following inheritance hierarchy: class Animal { public: void move() { cout << "Moving"; } }; class Bird : public Animal { public: void move() { cout << "Flying"; } }; class Fish : public Animal { public: void move() { cout << "Swimming"; } }; class Mammal : public Animal { public: void move() { cout << "Walking"; } }; 11
  • 12.
    Static Polymorphism: Problem1 To make a function callMove() to support every class in the animal inheritance hierarchy, we have to write/overload the callMove() function for every class. // 1 callMove() only. void callMove (Animal & a) Output: int main() { { ... Animal a; Moving a.move(); Bird b; } Moving Fish f; Moving // 4 callMove() for Mammal m; Moving // 4 different classes. callMove (a); void callMove (Animal & a) callMove (b); { ... a.move(); } callMove (f); Output: void callMove (Bird & b) callMove (m); { ... b.move(); } } Moving void callMove (Fish & f) Flying { ... f.move(); } Swimming void callMove (Mammal & m) Walking 12 { ... m.move(); }
  • 13.
    Static Polymorphism: Problem1 We can actually solve the problem in previous slides by using template. However the downside is the template function won't be limited to the Animal hierarchy anymore. // template callMove(). // Work but not limited to Animal hierarchy. template <typename T> void callMove (T & a) { ... a.move(); } 13
  • 14.
    Static Polymorphism: Problem2 We cannot use one for loop to call the correct version of move() method in an array/vector of objects from Animal hierarchy. Current output: int main() { Moving // Array of different animals. Moving Animal* a[4] = {new Animal, Moving new Bird, Moving new Fish, new Mammal}; Preferred output: for (int i = 0 ; i < 4; i++) a[i]->move(); Moving for (int i = 0 ; i < 4; i++) Flying delete a[i]; Swimming } Walking 14
  • 15.
    Dynamic Polymorphism • Thesolution to the upcasting problem we just discussed is to enable dynamic polymorphism or dynamic binding via the use of virtual function. • Requirements for C++ dynamic polymorphism: • There must be an inheritance hierarchy. • The superclass must have a virtual method. • Subclass must override superclass virtual method. • A superclass pointer/reference to a subclass object (upcasting) is used to invoke the virtual method. 15
  • 16.
    Virtual Function • Inorder to allow a method to be bound at run-time, it must be declared as virtual in the superclass. • By declaring a method as virtual in superclass, it is automatically virtual in all subclasses. class Animal { public: virtual void move() { // Enable dynamic binding. cout << "Moving"; } }; class Bird : public Animal { public: void move() { // Automatically virtual. cout << "Flying"; } }; 16
  • 17.
    Virtual Function  We can use the keyword virtual at subclass' virtual function to remind ourselves that it is using dynamic binding. class Animal { public: virtual void move() { cout << "Moving"; } }; class Bird : public Animal { public: virtual void move() { // Optional, as reminder. cout << "Flying"; } }; 17
  • 18.
    Dynamic Polymorphism After makingthe move() method virtual: int main() { Bird* b1 = new Bird; Animal* a1 = b1; // Upcasting via pointer. a1->move(); // cout "Flying". Bird b2; a1 = &b2; // Upcasting via pointer. a1->move(); // cout "Flying". Animal& a2 = b2; // Upcasting via reference. a2.move(); // cout "Flying". delete b1; } Bird::move() is called now, which is more appropriate/precise/accurate in the context. 18
  • 19.
    Dynamic Polymorphism: Benefit Theprimary benefit of dynamic polymorphism is it enables to write codes that work for all objects from the same inheritance hierarchy via upcasted pointer / reference. Consider the following inheritance hierarchy: class Animal { public: virtual void move() { cout << "Moving"; } }; class Bird : public Animal { public: virtual void move() { cout << "Flying"; } }; class Fish : public Animal { public: virtual void move() { cout << "Swimming"; } }; class Mammal : public Animal { public: virtual void move() { cout << "Walking"; } }; 19
  • 20.
    Dynamic Polymorphism: Benefit Wecan write a single callMove() function that call the correct version of move() method for all objects in the Animal hierarchy. void callMove (Animal & a) { ... a.move(); } Output: int main() { Animal a; Moving Bird b; Flying Fish f; Swimming Mammal m; Walking callMove (a); callMove (b); callMove (f); callMove (m); 20 }
  • 21.
    Dynamic Polymorphism: Benefit Wecan also write a loop to call the correct version of move() method for all objects in the Animal hierarchy. int main() { // Array of different animals. Output: Animal* a[4] = {new Animal, new Bird, Moving new Fish, Flying new Mammal}; Swimming for (int i = 0 ; i < 4; i++) Walking a[i]->move(); for (int i = 0 ; i < 4; i++) delete a[i]; } 21
  • 22.
    Abstract Class  Anabstract class is a class that cannot be instantiated.  A class that can be instantiated is called a concrete class. – All classes that we have learned so far are concrete classes.  The reason to make a class abstract is, due to some requirements/constraints, we want to make it impossible to create instance(s) of the class.  To be an abstract class: a class must have at least a pure virtual function. 22
  • 23.
    Pure Virtual Function A pure virtual function is a method that is initialized to zero (0) in its declaration and no implementation is given in the class.  Pure virtual function makes the class abstract and no instance of the class can be created. class Animal { // Abstract class. public: virtual void move() = 0; // Pure virtual function. }; int main() { Animal a1; // ERROR: Animal is an abstract class. Animal* a2 = new Animal; // ERROR: same reason. ... } 23
  • 24.
    Pure Virtual Function  Subclass of an abstract class must override all of the superclass pure virtual functions in order to become a concrete class (instantiate-able). class Animal { // Abstract superclass. public: virtual void move() = 0; // Pure virtual function. }; class Bird : public Animal { // Concrete subclass. public: virtual void move() { // Overriding pure virtual function. cout << "Flying"; } }; int main() { Animal *a = new Bird; // Fine. create a Bird object. a->move(); // cout "Flying“. } 24
  • 25.
    Pure Virtual Function  Subclass that does not override superclass' pure virtual function is an abstract class too. class Animal { // Abstract class. public: virtual greet() {} virtual void move() = 0; // Pure virtual function. }; class Bird : public Animal { // Abstract class. public: virtual void greet() { ... } }; class Eagle : public Bird { // Concrete class. public: virtual void greet() { ... } virtual void move() { ... } // Overriding. }; 25
  • 26.
    Virtual Destructor  Superclass destructor should always be virtual in order for the delete operator to call the destructors correctly for an subclass object that is created via upcasting.  Otherwise, the delete operator will call only superclass destructor, not the subclass destructor. – This will cause only the base part of the object to be destroyed. // Problem int main() { class Super { Super* p = new Sub; // Upcasting. public: delete p; // Invoke destructor. ~Super() { // Non virtual. } cout << "Super destroyedn"; } }; class Sub : public Super { Output: // Where is Sub's destructor? public: Super destroyed ~Sub() { cout << "Sub destroyedn"; } 26 };
  • 27.
    Virtual Destructor // Solution  To ensure that class Super { subclass objects are public: destroyed properly, virtual ~Super() { make superclass cout << "Super destroyedn"; } destructor virtual. }; class Sub : public Super { public: virtual ~Sub() { Output: cout << "Sub destroyedn"; } Sub destroyed }; Super destroyed int main() { Super* p = new Sub; // Upcasting. delete p; // Invoke destructor. } 27
  • 28.
    Good Programming Practices  Use inheritance to promote code reusability.  Use virtual function to describe common behavior within a family of classes.  Use pure virtual function to force subclasses to define the virtual function.  Use a pointer/reference to an abstract base class to invoke virtual function, thus implementing dynamic polymorphism. 28