Practicing Inheritance Concepts 1) Exercising Assignment #include using namespace std; // base class class A { public: A(int pi) : i(pi) { cout << "A" << endl; } ~A() { cout << "~A" << endl; } void get() { cout << "i = " << i << endl; } private: int i; }; // B is derived class: it possesses all the properties of A // e.g., can invoke get() function without defining it class B : public A { public: B(int pi, int pj) : A(pi), j(pj) { cout << "B" << endl; } ~B() { cout << "~B" << endl; } private: int j; }; int main() { A a(5); A *ap; /* prints A */ B b(2,3); /* prints A B */ a = b; /* B object can be assigned to an A object! */ ap = &b; /* pointer to B can be assigned to pointer to A */ /* shared function between A and B, prints "i = 2" */ b.get(); return 0; /* prints "~B ~A", "~A" */ } - typing rule - B object is an A object - A object is not a B object - typing rule relaxation in one direction! - b = a is invalid - bp = ap or bp = &a is invalid - after ap = &b has been assigned - ap can't invoke any functions unique to B 2) Exercising Constructor Ordering See the example above. - constructor of B called on object b in main - passes integer values 2 and 3 - B constructor calls A constructor - passes value 2 to A constructor via initializer list - A constructor initializes member "i" with passed value 2 - body of A constructor runs and outputs "A" - B constructor initializes member "j" with passed value 3 - body of B constructor runs and outputs "B" - derived class ctor calls base class default constructor automatically - unless some (other) base class constructor is called explicitly through passing an argument as in initializer list above - if base class has no default constructor then - derived class must call some other base class constructor 3) Destructor Ordering Again with the example above - B destructor called on object b in main - body of B destructor runs and outputs "~B" - B destructor calls member "j" "destructor" - int is a built-in type, so no-op - B destructor calls A destructor - body of A destructor runs and outputs "~A" - destructor calls member "i" destructor, again a no-op - compare dtor and ctor order: - at the level of each class - order of steps is reversed in ctor vs. dtor - ctor: base class, member ctors, body - dtor: body, member dtors, base class - suppose the initializer list of B's constructor was j(pj), A(pi) - even then A's constructor would be called first 4) Exercising Adding and Overriding Functions - e.g. #include using namespace std; class A { public: A(int pi) : i(pi) { cout << "A" << endl; } ~A() { cout << "~A" << endl; } void get() { cout << "i = " << i << endl; } private: int i; }; // B is derived class: it possesses all the properties of A // e.g., can invoke get() function without defining it class B : public A { public: B(int pi, int pj) : A(pi), j(pj) { cout << "B" << endl; } ~B() { cout << "~B" << endl; } void get() { cout << "j = " << j << endl; } // overrides base function void x() { cout << "B::x" << endl; } // added new function private: int j; }; int main() { A *ap; B *b = new B(2,3); /* outputs "A B" */ b->get(); /* outputs "j = 3" */ b->A::get(); /* outputs "i = 2" */ ap = b; ap->get(); /* outputs "i = 2". */ delete ap; /* outputs "~A". */ return 0; } - adding - new function x() added to class B - overriding - replacing - get() function in B overrides get() function in A - base function can be called using A::get() syntax 5) Exercising Virtual Functions - Manipulating Different Objects as a Group #include using namespace std; class A { public: A(int pi) : i(pi) { cout << "A" << endl; } virtual ~A() { cout << "~A" << endl; } // made virtual virtual void get() { cout << "i = " << i << endl; } // made virtual private: int i; }; // B is derived class: it possesses all the properties of A // e.g., can invoke get() function without defining it class B : public A { public: B(int pi, int pj) : A(pi), j(pj) { cout << "B" << endl; } virtual ~B() { cout << "~B" << endl; } // made virtual virtual void get() { cout << "j = " << j << endl; } // made virtual void x() { cout << "B::x" << endl; } private: int j; }; int main() { A *ap, a(5); /* outputs "A" */ B *b = new B(2,3); /* outputs "A B" */ b->get(); /* outputs "j = 3" */ ap = &a; ap->get(); /* outputs "i = 5"! */ ap = b; ap->get(); /* outputs "j = 3"! */ delete ap; /* prints "~B ~A"! */ return 0; /* prints "~A" */ } - virtual functions are used to support related objects - function declared virtual in a base class - can be overridden in derived class - virtual functions are resolved dynamically - e.g., destructors farther down the hierarchy get called - virtual functions only work with pointers or references - calls on object itself are resolved statically - note that virtual keyword need not be repeated in derived classes - but it's good style to do so - caller can force static resolution of a virtual function via scope operator - e.g., ap->A::get(); prints "i = 2" - overriding only happens when signatures are the same - otherwise new function overloads the function (or the operator name)