C++ Programming: Polymorphism 2018 년도 2 학기 Instructor: Young-guk Ha Dept. of Computer Science & Engineering
Contents Run-time binding in C++ Abstract base classes Run-time type identification 2
Function Binding in C++ Function binding To associate a function s name with its entry point, which is the starting address of the code that implements the function Types of function binding in C++ Compile-time binding To bind any invocation of a function to its entry point when the program is being compiled I.e., compiler determines what code is to be executed Has been in effect throughout our examples so far Run-time binding The alternative of the compile-time binding To bind a function when the program is running 3
Polymorphism Polymorphism is run-time binding of methods I.e., a method is polymorphic if its binding occurs at run-time rather than compile-time A powerful tool for programmers A programmer can invoke an object s polymorphic method without knowing exactly which class type or subtype is involved E.g., a Window class hierarchy Imagine a base class Window and its 20 or so derived classes Each class has a polymorphic display method with the same signature, which performs some class-specific operation The programmer needs to remember only one method signature to invoke 20 or so different methods 4
Example of Polymorphism Various versions of the display method Base class Window has a display method to draw basic window frames on the screen MenuWindow has a display to show a list of choices in addition Each Window class has its own version of display method A client can invoke a single display method for any type of Window at runtime E.g., w->display(); If w points to a Window object, its display is invoked Else if w points to a MenuWindow or DialogWindow object, each display is invoked Window* w Window ::display() MenuWindow DialogWindow ::display() ::display() Note that what version of display to be invoked is determined at runtime (not compile time) InformationDialog ::display() 5
Requirements for C++ Polymorphism Three requirements for implementation of polymorphism in C++ 1) When defining classes with polymorphic methods All the classes must be in the same inheritance hierarchy 2) When defining polymorphic methods The methods must be virtual and have the same signature 3) When using polymorphic methods There must be a pointer of base class, which is used to invoke the virtual methods No cast is required when assigning a pointer of base class to an object of derived class (* note that a pointer of base class may point to an object of any derived class) 6
Polymorphism Example // base class class TradesPerson { virtual void sayhi() { cout << Just hi. << endl; } // derived class 1 class Tinker : public TradesPerson { virtual void sayhi() { cout << Hi, I tinker. << endl; } // derived class 2 class Tailor : public TradesPerson { virtual void sayhi() { cout << Hi, I tailor. << endl; } int main() { // pointer to base class TradesPerson* p; int which; do { cout << 1 == TradesPerson, << 2 == Tinker, 3 == Tailor ; cin >> which; } while (which < 1 which > 3); } // set p depending on user choice switch (which) { case 1: p = new TradesPerson; break; case 2: p = new Tinker; break; case 3: p = new Tailor; break; } // run-time binding in effect p->sayhi(); delete p; // free the storage 7
Polymorphism Example (cont.) TradesPerson Object virtual void sayhi(); TradesPerson TradesPerson *p p->sayhi(); Just hi. Tinker Tailor Tinker Object virtual void sayhi(); Tailor Object virtual void sayhi(); *p p->sayhi(); Hi, I tinker. *p p->sayhi(); Hi, I tailor. 8
Implicit virtual Methods Using keyword virtual in all the polymorphic methods is not a requirement If a base class method is declared virtual, any derived class method with the same signature is automatically declared virtual, even if not explicitly declared E.g., in the previous example, keyword virtual in front of method Tinker::sayHi can be omitted class TradesPerson { virtual void sayhi() { cout << "Just hi." << endl; } class Tinker : public TradesPerson { void sayhi() { cout << "Hi, I tinker." << endl; }... 9
Notices on Using Keyword virtual In case of defining a virtual method outside the class, the keyword virtual occurs only in the method s declaration, not in its definition class C { virtual void m(); // declaration: "virtual" occurs... void C::m() {... } // definition: no "virtual" Top-level functions can not be virtual, only methods can be virtual virtual void f(); int main() {... } // ERROR: not a method! 10
Inheriting virtual Methods virtual methods can be inherited from base classes to derived classes just like common methods E.g., class Tinker does not provide its own definition of sayhi, it inherits sayhi from its base class TradesPerson class TradesPerson { virtual void sayhi(){ cout << "Just hi." << endl; } class Tinker : public TradesPerson {... // no Tinker s sayhi defined int main() { Tinker t1; t1.sayhi();... } // inherited TradesPerson s sayhi Just hi. 11
The Vtable OOP languages commonly use the vtable (virtual table) to implement run-time binding Implementation of vtable is system-dependent The purpose of vtable is to support run-time lookup of a particular entry point for a function s name Thus, a program using run-time binding incurs performance penalty due to vtable lookup Space: amount of storage for the vtable Time: time taken for vtable lookup E.g., Smalltalk vs. C++ In a pure OOP language such as Smalltalk, all methods are polymorphic by default suffers from heavy performance penalty In C++, programmer can choose polymorphic (virtual) methods smaller performance penalty 12
Vtable Example class B { // base class virtual void m1() {... } virtual void m2() {... } class D : public B { // derived class virtual void m2() {... } int main() { B b; // base class object D d; // derived class object B* p; // pointer to base class virtual Method Name b.m1 b.m2 d.m2 [ Vtable ] Entry Point 0x7723 0x23b4 0x99a7 } p = &b; // p is set to b s address p->m2(); // vtable lookup for run-time binding p = &d; // p is set to d s address p->m2(); // vtable lookup for run-time binding 13
Constructors and Destructors A constructor can NOT be virtual, but a destructor CAN be virtual class C { virtual C(); virtual C(int a); virtual ~C(); virtual void m(); // ERROR: a constructor // ERROR: a constructor // OK: a destructor // OK: a regular method 14
virtual Destructors In some cases, destructors should be made virtual to ensure that polymorphism is at work for the destructors of the derived classes E.g., if a derived class has a data member that points to dynamically allocated storage and the destructor to free such storage storage leakage problem can occur when handling the derived class with a pointer to its base class (refer to the next example) In commercial libraries (e.g., MFC), destructors are typically virtual to prevent such problem 15
virtual Destructors Example Example codes of the storage leakage problem class A { A() { p = new char[5]; } ~A() { }... delete[] p; // free p class Z : public A { Z() { q = new char[5000]; } ~Z() { }... delete[] q; // free q void f() { A* ptr; // type of ptr is pointer // of the base class A ptr = new Z(); // both A() and Z() fire // a Z object is created delete ptr; // only ~A() fires, not ~Z() // no polymorphism // (ptr is a pointer of // class A) } // Caution: results in 5,000 bytes // of leaked storage 16
virtual Destructors Example (cont.) The previous problem can be fixed by making the destructors virtual By making the base class destructor ~A() virtual, we can make derived class destructor ~Z() virtual implicitly class A {... virtual ~A() { delete[ ] p; }... // virtual destructor // ~Z() becomes virtual as well void f() {... delete ptr; // ~Z() fires and then ~A() fires // checks what ptr actually points to,... // not the type of ptr } A() firing Z() firing ~Z() firing ~A() firing 17
Class Methods and Run-Time Biding static methods (class methods) can not be virtual class C { // ERROR: static and virtual static virtual void f(); static void g(); // OK, not virtual virtual void h(); // OK, not static 18
Other C++ Constructs That Resembles Run-Time Binding In C++, only virtual methods are bound at run-time Only virtual methods are truly polymorphic C++ has some other constructs that resemble runtime binding but are quite distinct from it (actually, compile-time binding) Name overloading (both methods and functions) Name overriding Name hiding 19
Name Overloading When top-level functions or methods in the same class share one name, but have different signatures class C { C() {... } // default constructor C(int x) {... } // convert constructor void f(double d) {... } void f(char C) {... } int main() { C c1; C c2(26); f(3.14); f('z ); } // default constructor called // convert constructor called // f(double) is bound by compiler // f(char) is bound by compiler 20
Name Overriding When a base class and its derived class have methods with the same signature (not declared virtual) class B { // base class void m() { cout << "B::m" << endl; } // m is not virtual class D : public B { // derived class void m() { cout << "D::m" << endl; } // overrides B::m int main() { } D q; q.m(); B* p; p = &q; p->m(); // invokes D::m // (D::m overrides B::m) // invokes B::m (not D::m) // if B::m is declared virtual class B { // base class virtual void m() { cout << "B::m" << endl; }... int main() { }... B* p; p = &q; p->m(); // invokes D::m // (polymorphism) 21
Name Hiding When a base class and its derived class have methods with the same name and different signatures class B { void m(int x) { cout << x << endl; } class D : public B { void m() { cout << "Hi" << endl; } int main() { D d; d.m(); // OK d.m(26); // Error: B s m(int) is hidden by D s m() // to correct, use d1.b::m(26) return 0; } 22
Name Sharings in C++ Run time binding Polymorphism Between virtual methods of classes in the same hierarchy Have the same signature Name overriding Between methods (not virtual) of classes in the same hierarchy Have the same signature Compile time binding Name hiding Between methods (not virtual) of classes in the same hierarchy Have the same name but different signatures Name overloading Between methods in the same class or top-level functions Have the same name but different signatures 23
Another Usage of virtual: Shared Interfaces In OO design, sometimes, it is required that different classes must commonly define a collection of specific methods It is called shared interface (cf. interface in Java) E.g., a Window class hierarchy designer may want to ensure that each derived class in the hierarchy defines its own display, close, and move methods We can use a kind of virtual methods to enforce derived classes to define (override) those methods Note that generic virtual methods could be, but need not be overridden in derived classes class B { // base class virtual void m() {... } class D : public B { // derived class //... no m() is defined in D // thus B::m is just inherited (need not be overridden) 24
Pure virtual Methods Pure virtual methods Methods that any derived classes must override in order to instantiate their objects Declarations end with the special syntax =0 Have no definitions // a class with pure virtual methods class ABC { virtual void open() = 0; virtual void close() = 0; virtual void flush() = 0; 25
Abstract Base Classes What is an abstract base class (ABC) A class that has one of more pure virtual methods Just one pure virtual method suffices to make a class abstract ABCs can NOT instantiate their objects because they are abstract ABC is typically used as a shared interface in C++ class ABC { // Abstract Base Class virtual void open() = 0;... ABC obj; // ERROR: ABC is an abstract class 26
ABC and Derived Classes Although an ABC can not have any object that instantiates it, it can have derived classes Classes derived from an ABC MUST override all of the ABC s pure virtual methods Otherwise, the derived classes also become abstract and no objects can instantiate them class ABC { // Abstract Base Class virtual void open() = 0; class X : public ABC { // 1st derived class virtual void open() {... } // overrides open() class Y : public ABC { // 2nd derived class // *** open is not overridden ABC a1; // *** ERROR: ABC is abstract X x1; // *** OK, X overrides open() and is not abstract Y y1; // *** ERROR: Y is also abstract open() is not defined 27
ABC and Other Members ABCs may have other members than pure virtual methods, which can be normally inherited Data members Constructors and destructors Ordinary methods and virtual methods class ABC { // Abstract Base Class ABC() {... } ABC(int x) {... } ~ABC() {... } virtual void open() = 0; virtual void print() const {... } int getcount() const { return n; } private: int n; // constructor // constructor // destructor // pure virtual // virtual // nonvirtual // data member 28
Restriction on Pure Declarations Only virtual methods can be pure Neither nonvirtual methods nor top-level functions can be declared pure void f() = 0; // **** ERROR: not a method class C { void open() = 0; // **** ERROR: not virtual... 29
Usage of ABC (1) class IFile { // file interface virtual void open() = 0; virtual void close() = 0; virtual void flush() = 0; class TextFile : public IFile { virtual void open() {... } virtual void close() {... } virtual void flush() {... } class BinaryFile : public IFile { virtual void open() {... } virtual void close() {... } virtual void flush() {... } // to open a text file // to close a text file // to flush a text file // to open a binary file // to close a binary file // to flush a binary file 30
Usage of ABC (2) To specify program design requirements E.g., introspection methods for a big project A large number of programmers: each programmer is responsible for a number of classes During the design phase, the PM decided that each class should have two public methods to introspect the class Such design decision can be enforced by using an ABC class IIntrospect { // introspection interface virtual void listdata() = 0; // prints data members info. virtual void listmethods() = 0; // prints methods info. } 31
Run-Time Type Identification C++ supports run-time type identification, which provides mechanisms To check if type conversion is safe at run time Using dynamic_cast operator To determine type of object or class at run time Using typeid operator 32
Dynamic Cast Previously discussed cast operators in C++ static_cast Can be applied to any type (polymorphic or not) E.g., Converting int a to float b b = static_cast<float>(a) == b = (float)a Static cast (at compile-time) does not ensure type safety at run-time const_cast Used to cast away constness of a pointer to a const object reinterpret_cast Used to convert from a pointer of one type to a pointer of another type dynamic_cast The dynamic cast operator tests at run-time whether a cast involving objects is type safe or not 33
Type Safety Problem of static_cast at Run-Time A derived class pointer actually points to a base class object class B { virtual void f() {... } // note: no method m class D : public B { void m() {... } // additional method m of class D int main() { D* p; // pointer to derived class p = new B; // compile-time error: cast required p = static_cast<d*>(new B); // legal but dealt w/ caution p->m(); // run-time error: even though p is a pointer of D // it actually points to a B object 34
Type Safety Problem Illustrated D* p; X B_Object D* p; D_Object D* p; D_Object void f(); void f(); void m(); void f(); void m(); static_cast<d*>(&b_object) B_Object p->m(); void f(); 35
dynamic_cast to Resolve Type Safety Problem C++ provides dynamic_cast operator to check at run-time whether a cast is type safe or not dynamic_cast<target_type_ptr>(source_obj_ptr) Evaluates to target_obj_ptr (true), if the cast is type safe Evaluates to 0 (NULL or false), if the cast is not type safe Notices on using dynamic_cast A dynamic_cast is legal only for polymorphic types The source type (type of source_obj_ptr) must have a virtual method The target type and the source type must be on the same inheritance hierarchy Target type must be specified as a pointer or a reference dynamic_cast<t>(ptr) Error Dynamic_cast<T*>(ptr) OK 36
dynamic_cast Example class B { virtual void f() {... } class D : public B { void m() {... } Error: not safe int main() { D* p; // pointer to derived class p = dynamic_cast<d*>(new B); if (p) p->m(); // if type safe then invoke p->m() else cerr << Error: not safe << endl; 37
Checking Parameter s Type at Run-Time with dynamic_cast virtual void printtitle() class Book class Fiction virtual void printtitle() class Textbook virtual void printtitle() void printlevel() void printbookinfo(book* bookptr) invokes bookptr->printtitle() invokes bookptr->printlevel() not safe 38
Checking Parameter s Type at Run- Time with dynamic_cast (cont.) class Book { virtual void printtitle() const {... }... } class Textbook : public Book { void printtitle() const {... } void printlevel() const {... }... } class Fiction : public Book { void printtitle() const {... }... } void printbookinfo(book* bookptr) { bookptr->printtitle(); // print title in any case Textbook* ptr = dynamic_cast<textbook*>(bookptr); if (ptr) ptr->printlevel(); // invoke printlevel if type safe } 39
downcast upcast The Rules for dynamic_cast Simple rules for dynamic_cast dynamic_cast from a directly or indirectly derived type to a base type succeeds (upcast) E.g., dynamic_cast<b*>(new D1) will succeed dynamic_cast from a base type to a directly or indirectly derived type may fail (downcast) E.g., dynamic_cast<d1*>(new B) may fail dynamic_cast between unrelated types (other than void) fails E.g., dynamic_cast<d1*>(new Z) or vice versa will fail B Z D1 : success D2 D3 : fail : may fail 40
Run-Time Type Determination with the typeid Operator The typeid operator is used to determine an expression s type at run-time typeid(typename expression) Returns a reference to a type_info object that represents the given typename or expression typeinfo header needs to be included first E.g., given float x and long val typeid Statement typeid( x ) == typeid( float ) typeid( x ) == typeid( double ) typeid( x ) == typeid( float* ) typeid( val ) == typeid( long ) typeid( val ) == typeid( short ) typeid( 5280 ) == typeid( int ) typeid( 9.21883E-9L ) == typeid( long double ) Evaluation true false false true false true true 41
Run-Time Type Determination with the typeid Operator (cont.) E.g., given the previous Book class hierarchy and the following declaration Book* bookptr = new Textbook( test, 1); typeid Statement Evaluation typeid( bookptr ) == typeid( Book* ) typeid( bookptr ) == typeid( Textbook* ) typeid( *bookptr ) == typeid( Book ) typeid( *bookptr ) == typeid( Textbook ) true false false true 42