3.4 Content: 1. Inheritance 2. Redefinition & Include Guard 3. Liskov Substitution Principle 4. Slicing 5. Function overloading 6. Initialization of Classes in a Inheritance hierarchy Inheritance: We don't re-use by copying, we re-use by sharing Parent Class (Base Class) | Child Class (Derived Class) class Student : public UPerson { private: float GPA; Course* enrolled; int num_courses; public: Student(string n, Department d,float x): UPerson(n,d),GPA(x),enrolled(nullptr),num_courses(0){} // Using Parent class Constructor in MIL // Must in the MIL! Cannot be used in elsewhere } Base Class cannot call Derived Class's member function Derived Class can call Base Class's member function Derived Class can call Its own member function Children class inherit: 1. data member from base class 2. member function in base class Redefinition(Error): main.cpp ---include---> student.h & teacher.h teacher.h ---include---> UPerson.h student.h ---include---> UPerson.h --> UPerson Has been "Include" TWICE in main.cpp! Since Include is like Copy("#include" == "Copy & Paste"), There is redefinition ( We define "UPerson" TWICE!) How to prevent?: "Conditional Include" """ if we have included then do not include if we have not then include """ Preprocessor directives --> "Include guard" # ifndef _UPERSON_H // if not defined # define _UPERSON_H /// MAIN PART/// #endif Polymorphic or Liskov Substitution Principle Since Student inherits from UPerson, "A Student obj can be treated like a Upreson obj" A STUDENT OBJECT IS A UPERSON OBJECT! --> all method of UPERSON can be called by a Student object Since a Student/Teacher object is also a UPerson object, any functions defined on UPerson objects may also be called by any Student and Teacher objects Remark: UPerson cannot use Student/Teacher's own member function If : D inherits from B Then: 1. Every D object is also a B object, but not vice-versa. 2. B is a more general concept; D is a more specific concept. 3. Wherever a B object is needed, a D object can be used instead. Function Expecting an Argument of Type | Will Also Accept ---------------------------------------|----------------------- UPerson | Student pointer to UPerson | pointer to Student UPerson reference | Student reference UPerson* ptr1 = &student1 // Okay! void print(Upreson uperson){...} print(student1)// Okay! void print2(Student student){...} print(uperson1) // Error, Upreson is not a Student object! "Slicing": If B Obj = dObj; then Obj is sliced from dObj --> Losing D Obj's own FEATURES! Function overloading: print(const UPerson* uperson) print(const UPerson& uperson) print(const Student& student) print(const Student* student) study(const Student& student) study(const Student* student) UPerson p; Student s; print(s) ->print(const UPerson& uperson) print(p) ->print(const UPerson& uperson) print(&s) ->print(const UPerson* uperson) print(&p) ->print(const UPerson* uperson) study(s) ->study(const Student& student) study(p) ERROR! study(&s) ->study(const Student* student) study(&p) ERROR! if add: print(const Student& student) print(const Student* student) print(s) print(const Student& student) // Best Match print(p) print(const UPerson& uperson) print(&s) print(const Student* student) // Best Match print(&p) print(const UPerson* uperson) study(s) study(const Student& student) study(p) ERROR study(&s) study(const Student* student) study(&p) ERROR Direct and indirect Inheritance: indirect inherit --> can be treated as direct base class obj & indirect base class obj Initialization of Classes in a Inheritance hierarchy Derived Class initialization must call Base class constructor -> since Base class has Private Data member which Derived class cannot Access 3.9 Content: 1. Order of Con/Destruction with Inheritance 2. Problems with inheritance Order of Con/Destruction w/ Inheritance: Construction: 1. Base class( Its parent) 2. Data member 3. Derived class Remark: A inherits B then: B obj; B:this = A:this Destruction: 1. Derived class 2. Data member 3. Base class Order of Con/Destruction w/ Inheritance & Delegating Constructor: B():B(0,1){} B(int a, int b):A(n),b(m){} ~B(){} A():A(1){} A(int a):A(a){} ~A(){} Construction: A(int a):A(a){} A():A(1){} B(int a, int b):A(n),b(m){} B():B(0,1){} Problem with Inheritance: 1. Slicing: assingment from derived class to base class -> lose features B* bptr = new D(300,400); delete bptr // Error! It is actually a D obj but we're using B's destructor "static_cast" --> delete static_cast(bptr) // Okay OR: Polymorphism "virtual" 2. Name Conflicts class B { private: int x,y; public: f(); } class D: public B{ private: float x,y; public: f(); } x,y and f() from B have being hiden (this is not overloading) How to call x,y,f() from B? Using scope identifier "::" 3.11 Content: 1. Member access control 2. Polymorphism & “Virtual” keyword (Dynamic Binding) UPerson::print() Student::print() This is not function overloading! How to call UPerson::print() in a student Obj? UPerson::print()! Derived Class cannot access Base class's data member! -> Have been declared "private" Solution: "protected" class UPerson{ protected: string name; Department dept; public: ... } protected: access from itself Okay! access from derived class Okay! access from outside NO! Member access control protected vs private if protected: all derived class can access base class data member hard to update it is preferable to use private instead of protected public: accessible to 1. member functions of the class (from class developer) 2. any member functions of other classes (application programmers) 3. any global functions (application programmers) protected: accessible to 1. member functions and friends of the class 2. member functions and friends of its derived classes (subclasses)⇒ class developer restricts what subclasses may directly use private: accessible only to 1. member functions and friends of the class ⇒ class developer enforces information hiding Remark: Without inheritance, private and protected control are the same Virtual: virtual keyword Base baseObj; baseobj.f(); Derived derivedObj; derivedObj.f() //Liskov Pointer: Base* basePtr1 = & baseObj; Base* basePtr2 = & DerivedObj; basePtr1->f(); // call Base::f() basePtr2->f(); // if not "virtual": call Base::f() since it is baseptr // if virtual : call Derived:: f()! Ref: Base& baseRef1 = baseObj; Base& baseRef1 = derivedObj; baseref1.f() // call Base::f() baseRef2.f() // if not "virtual": call Base::f() since it is baseptr // if virtual : call Derived:: f()! // Why? it is decided during execution! Dynamic binding if a method is declared virtual in the base class it is automatically virual in ALL derived classes! polymorphism --> CHANGING SHAPE a pointer or reference must take advantage of polymorphism! why not value --> value passed by "slicing" there is no changing shape RTTI: Run Time Type Information keeps track of dynamic types typeid() & typeid().name Base* ptr = new Derived; typeid(ptr).name -> 7Derived! dynamic_cast<>() if pointer & invalid -> nullptr if ref & invalid -> Runtime Error! dynamic_cast<>() and static_cast<>(): static_cast: NO RTTI Check! Base* BasePtr = new Derived; static_cast(BasePtr) -> No error, the program can be compiled Base* BasePtr = new Base; static_cast(BasePtr) -> undefined behaviour! Though it ALSO can be compiled While: Base* BasePtr = new Base; dynamic_cast(BasePtr) -> nullptr! no undefined behaviour! -> it is more safer than static_cast 3.16 Content: 1. Virtual function 2. Dynamic Binding (typeid) 3. Overloading & Overriding 4. Virtual in Destructor/Constructor 5. ABC about virtual function's implementation: class A { public: virtual void f() const; // declaration } void A:f() const{ ... } //we should not write "virtual" here (implementation) again! Base* baseptr = new DeriveFromDerived; Base class has a virtual f() Derived class has a f() Derived Derived class has a f() baseptr->f(); --> call Derived Derived class's f()! why? if base class's function is "virtual" then the same function in derived class /derived derived class /... is all "virtual"! How to compare different objects? (i.e. check real identity of an object) typeid() typeid().name 1. typeid(ptr1) == typeid(ptr2) 2. strcmp(typeid(ptr1).name,typeid(ptr2).name) // typeid().name is actually “const char*”! // don‘t write "typeid(ptr1).name == typeid(ptr2).name"! 3. dynamic_cast(basePtr) != nullptr (though maybe ancestor) Remarks: for dynamic_cast<>(): if dealing with pointer & invalid(e.g. down casting) -> nullptr if dealing with ref & invalid -> Runtime Error! --> we better not dynamic cast reference "override" keyword: 1. Overriding is not possible if the method is not virtual. class Base { public: virtual void f(int a) const { cout << a << endl; } }; class Derived: public Base { int x {25}; public: void f(int) const override; // prototype must be IDENTICAL with base class function! }; void Derived::f(int a) const {} // (don't write "override" again when implementation!) Remark: 如果基类没有使用 virtual,子类写 override 不仅没用,还会导致编译错误! Virtual Or Non-virtual? If the method works exactly the SAME for all derived classes, it should not be a virtual function. If the precise behaviour of the method "depends on the object", it should be a virtual function Overriding vs Overloading: Overloading: f(); f(int a); same name , different parameter --> overloading Overriding: Base class : virtual f(); f(int a); Derived class : f(); Derired derivedObj; derivedObj.f(100);//error! overriding will actually "hides" the f() 名称隐藏(Name Hiding) :当派生类定义了一个同名函数 f() 时,它会隐藏基类中所有同名的函数,不管参数是否相同 Derived class : f(); --> "hides" Base:: f(int a)! though not same prototype C++ 的名称查找按照以下顺序进行: 先在 Derived 类的作用域 查找名字 f 找到了 Derived::f()(无参数版本) -> 停止查找! 不会继续在基类中查找其他重载版本 进行参数匹配,发现参数不匹配 → 编译错误! Virtual in Destructor: Base* basePtr = new Derired; // Derired has its own dynamic allocated data member! delete basePtr; // Error, Memory Leak! What we do before "virtual" : delete static_cast(basePtr); Now: class Base{ public: virtual ~Base(){} } class Derived{ public: ~Derived(){} } Base* basePtr = new Derired; delete basePtr; // call ~Derived! since polymorphism -> No Erorr! Order: if no virtual: 1. Base class constructor 2. Derired class constructor 3. Base class constructor // Probably an error if virtual: 1. Base class constructor 2. Derired class constructor 3. Derived class destructor 4. Base class destructor Calling virtual function in Constructor: class Base { public: Base() { cout << "Base’s constructor\n"; f(); } virtual void f() { cout << "Base::f()" << endl; } }; class Derived : public Base { public: Derived() { cout << "Derived’s constructor\n";f(); } virtual void f() override { cout << "Derived::f()" << endl; } }; Base* p = new Derived; Base’s Constructor Base::f() // here, though "virtual" ,constructor still call Base::f() // Why? Derived class is not completely initialized yet! Derived's constructor Derived::f() // here B is constructed , then vitrual function B::f() should be called //虽然对象还未完全构造,但此时: 虚函数表(vptr)已经被设置为指向 B 类的虚函数表 这是因为在进入 B 构造函数体之前,编译器已经完成了虚函数指针的初始化 所以虚函数调用能够正确派发到 B::f() Remarks Do not rely on the virtual function mechanism during the execution of a constructor. This is not a bug, but necessary — how can the derived object provide services if it has not been constructed yet? (没初始化完成的class无法提供member function -> Base class function) Similarly, if a virtual function is called inside the base class destructor, it represents base class’ virtual function: when a derived class is being deleted, the derived-specific portion has already been deleted before the base class destructor is called! (derived class 先被删除, 已经不完整的class, 无法提供member function -> Base class function) Remark: class Base{ Base(const Base&){}; } class Derived: public Base{ } Derived obj; Base(obj); Here, since polymorphism, "const Base&" accept "Derired" input So no conversion constructor involved Output: Derived default constructor Base copy constructor ABC: Abstract Base Class "Pure virtual function" e.g. virtual double compute_net_worth() const = 0; no need to be defined for this class -> have to be "override" in derived class! ABC: A class with Pure virtual function Abstract class{ ... virtual double compute_net_worth() const = 0; } Abstract obj; // Error! You cannot initialize an ABC object! 3.18 Content: 1. ABC / Pure virtual function 2. "final" keyword 3. Further reading(protected/private inheritance) 4. Function template ABC: class Shape{ public: double get_square() = 0; // pure virtual function } class Square:public Shape{ public: Square(){} // no "double get_square()" } Shape obj; // Error ! Cannot create ABC obj Square square; // Error! An ABC has two properties: 1. No objects of ABC can be created. 2. Its derived classes must implement the pure virtual functions, otherwise they will also be ABC’s. ABC as "interface": An abstract base class provides a uniform interface to deal with a number of different derived classes. An abstract base class cannot be used: 1. as an argument type that is passed by value 2. as a function return type that is returned by value 3. as the type of an explicit conversion "final" keyword class A {}; class B: public A {}; class C final: public B {}; // cannot furthe inherit C! class D: public B {}; class E: public C {}; E obj; // Error class Student : public UPerson { public: /* Other data and functions */ virtual void print() const override final { /* incomplete */ } // Derired Class cannot further override "print" }; class PG_Student : public Student { public: /* Other data and functions */ virtual void print() const override { /* incomplete */ } //Error! } Further Reading: Template: int larger(int a,int b){} double larger(double a, double b){} .... (Too many same function!) How to optimize code reusing? template const T& larger(const T& a, const T& b) { return (a>b)? a:b }