260 likes | 394 Views
159.234 LECTURE 17. 17. More on Inheritance, Virtual Inheritance, & Virtual Destructors,. More on Inheritance. Parent Constructors and Destructors. Let’s consider the following inheritance hierarchy:. 1. class X { public: X() { cout << "X::X() Ctor executing. " << endl; }
E N D
159.234LECTURE 17 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors,
More on Inheritance Parent Constructors and Destructors Let’s consider the following inheritance hierarchy: 1 class X { public: X() { cout << "X::X() Ctor executing. " << endl; } ~X() { cout << "X::~X() Dtor executing." << endl;} }; class Y : public X { public: Y() { cout << "Y::Y() Ctor executing. " << endl; } ~Y() { cout << "Y::~Y() Dtor executing." << endl;} }; Parent Ctor and Dtor.cpp
More on Inheritance Parent Constructors and Destructors class Z : public Y { public: Z(int n) { cout << "Z::Z(int) Ctor executing. " << endl; } ~Z() { cout << "Z::~Z() Dtor executing." << endl; }; When Z is declared, its ctor Z::Z(int) is called. Before executing, it calls the Y::Y() ctor which immediately calls the X::X() ctor. After X::X() ctor finishes, control is returned to Y::Y(). As soon as Y::Y() finishes, Z::Z() gains control and finishes last. Parent Ctor and Dtor.cpp
More on Inheritance Parent Constructors and Destructors * Parent default constructors execute in top-down order. X::X() Ctor executing. Y::Y() Ctor executing. Z::Z(int) Ctor executing. Z::~Z() Dtor executing. Y::~Y() Dtor executing. X::~X() Dtor executing. Parent Ctor and Dtor.cpp
More on Inheritance Parent Constructors and Destructors Let’s consider the following inheritance hierarchy: 2 class Person { public: Person(const char* s) { cout << "Person::Person() ctor." << endl; name = new char[strlen(s)+1]; strcpy(name,s); } ~Person(){ delete [] name; cout << "Person::~Person() Dtor." << endl; } protected: char *name; }; Parent Ctor and Dtor - Dtor calls.cpp
More on Inheritance Parent Constructors and Destructors Class derived from Person class Student: public Person { public: Student(const char* s, const char* m) : Person(s) { cout << "Student::Student() ctor." << endl; major = new char[strlen(m)+1]; strcpy(major,m); } ~Student(){ delete [] major; cout << "Student::~Student() dtor." << endl; } protected: char *major; }; Parent Ctor and Dtor - Dtor calls.cpp
More on Inheritance Parent Constructors and Destructors The main function: int main() { Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; } Parent Ctor and Dtor - Dtor calls.cpp
More on Inheritance Parent Constructors and Destructors int main() { Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; } Person::Person() ctor. Person::Person() ctor. Student::Student() ctor. Student::~Student() Dtor. Person::~Person() Dtor. Person::~Person() Dtor. When x is instantiated, the Person ctor is called, allocating 9 bytes of memory to store the string “Scratchy”. Next, y instantiates, first calling the Person ctor, which allocates 6 bytes to store the string “Itchy” and then allocating 8 more bytes to store the String “Biology”. Parent Ctor and Dtor - Dtor calls.cpp
More on Inheritance Parent destructors execute in a bottom-up order. * int main() { Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; } Person::Person() ctor. Person::Person() ctor. Student::Student() ctor. Student::~Student() Dtor. Person::~Person() Dtor. Person::~Person() Dtor. When the scope of y terminates, y’s dtor deallocates the 8 bytes used for “Biology”and then calls the Person dtor which deallocates the 6 bytes used for “Itchy”. Finally, the Person dtor is called to Destroy x, deallocating the 9 bytes used for “Scratchy” Parent Ctor and Dtor - Dtor calls.cpp
More on Inheritance 17 Parent Constructors and Destructors Summary: In an inheritance hierarchy, each constructor invokes its parent constructor before executing itself, and each destructor invokes its parent destructor after executing itself.
More on Inheritance 17 Virtual Destructors Virtual functions are overriddenby functions that have the same ‘signature’ and are defined in derived classes. Since the names of constructors and destructors are unique for each class, it leads us to think that they cannot be declared virtual. That is only true for constructors. Constructors are always unique for each class. On the other hand, destructors could be made virtual.
More on Inheritance 17 Memory Leak Consider the following class named X: class X { public: X() { p = new int[2]; cout << "X(). "; } ~X() { delete [] p; cout << "~X()." << endl;} private: int *p; }; Virtual Destructor - Memory Leak.cpp
Virtual Destructor 17 Memory Leak And another class named Y, derived from X: class Y : public X { public: Y() { q = new int[1023]; cout << "Y() : Y::q = " << q << ". "; } ~Y() { delete [] q; cout << "~Y(). ";} private: int *q; }; Virtual Destructor - Memory Leak.cpp
Virtual Destructor 17 Memory Leak Each iteration creates a new instance of Y Where is the memory leak here? This loop would invoke the base class’s constructor, as well as Y’s constructor, Allocating 4100 bytes (4 bytes for each int) Note: 1023*4 + 2*4 = 4100 bytes int main() { for(int i=0; i < 8; ++i) { X *r = new Y; delete r; } return 0; } Virtual Destructor - Memory Leak.cpp
Virtual Destructor 17 Memory Leak r is declared to be a pointer to X objects. Only the X destructor is invoked! It deallocates only 8 bytes, and so 4092 bytes are lost! int main() { for(int i=0; i < 8; ++i) { X *r = new Y; delete r; } return 0; } Let’s see the program in action. Virtual Destructor - Memory Leak.cpp
Virtual Destructor 17 Program Output X(). Y() : Y::q = 0x3d37c8. ~X(). X(). Y() : Y::q = 0x3d47d0. ~X(). X(). Y() : Y::q = 0x3d57d8. ~X(). X(). Y() : Y::q = 0x3d67e0. ~X(). X(). Y() : Y::q = 0x3d77e8. ~X(). X(). Y() : Y::q = 0x3d87f0. ~X(). X(). Y() : Y::q = 0x3d97f8. ~X(). X(). Y() : Y::q = 0x3da800. ~X(). Everything but dynamically allocated objects (that is, local objects, temporary objects, member objects, static objects, and array elements) are destructed in the reverse order of construction: first constructed is last destructed. Only the X destructor is invoked! So, how can we invoke the Y destructor? Virtual Destructor - Memory Leak.cpp
Virtual Destructor 17 To plug this memory leak, class X { public: X() { p = new int[2]; cout << "X(). "; } virtual ~X(){ delete [] p; cout << "~X()." << endl; } private: int *p; }; Change the base class’s destructor into a virtual function. Rule of thumb: Declare the base destructor virtual whenever your class hierarchy uses dynamic binding! Virtual Destructor - Memory Leak Resolved.cpp
Virtual Destructor 17 Program Output X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). X(). Y() : Y::q = 0x3d37c8. ~Y(). ~X(). Each iteration of the loop calls both destructors, restoring all memory that was allocated by the new operator. Virtual Destructor - Memory Leak Resolved.cpp
More on Inheritance Base Derived1 Derived2 Join 17 “Dreaded Diamond” of Class Inheritance Just something to be aware of… Base is inherited twice, which means that any data members declared in Base will appear twice within a Join object. This can create ambiguities: which data derived from Base would you want to change? Also, there’s an ambiguity In converting from Join* to Base*, or from Join& to Base&.
More on Inheritance 17 “Dreaded Diamond” of Class Inheritance class Base{ public: protected: int data; }; Base Derived1 Derived2 Join class Derived1 : public Base {..} class Derived2 : public Base {..} int main() { Join* j = new Join(); Base* b = j; return 0; } class Join : public Derived1, public Derived2{ public: void method(){ data = 1; } } ambiguous DreadedDiamond.cpp
More on Inheritance Base Derived1 Derived2 Join 17 “Dreaded Diamond” of Class Inheritance C++ lets us resolve the ambiguities using full qualification of data (e.g. Derived2::data = 1). Likewise, you could convert from Join* to Derived1* and then to Base*. That is not the best solution however.
More on Inheritance Base Derived1 Derived2 Join 17 “Dreaded Diamond” of Class Inheritance The best solution is to tell the compiler that there should be only one copy of the data member(s) derived from Base that should appear within a Join object. virtual virtual Use the virtual keyword in the inheritance part of the classes that derive directly from the top of the diamond.
More on Inheritance 17 “Dreaded Diamond” of Class Inheritance class Base{ public: protected: int data; }; Therefore, an instance of Join will have only a single Base subobject. This eliminates ambiguities. class Derived1 : public virtualBase {..} class Derived2 : public virtualBase {..} int main() { Join* j=new Join(); Base* b=j; return 0; } class Join : public Derived1, public Derived2{ public: void method(){ data = 1; } } good! DreadedDiamond.cpp
More on Inheritance 17 “Delegate to a sister class” via virtual inheritance class Base{ public: virtual void foo() = 0; virtual void bar() = 0; }; class Join: public Derived1, public Derived2{}; int main() { Join* p1 = new Join(); Derived1* p2 = p1; Base* p3 = p1; p1->foo(); p2->foo(); p3->foo(); return 0; } class Derived1 : public virtual Base{ public: virtual void foo(); }; ? void Derived1::foo() { bar(); } class Derived2: public virtual Base{ public: virtual void bar() { cout << "void Derived2::bar()" << endl;} }; Delegate to a sister class.cpp How does Derived1 know anything about bar()?
More on Inheritance 17 “Delegate to a sister class” via virtual inheritance Believe it or not, when Derived1::foo() calls this->bar(), it ends up calling Derived2::bar(). A class Derived1 knows nothing about will supply the override of a virtual function invoked by Derived1::foo(). This ‘cross delegation’ can be a powerful technique for customizing the behaviour of polymorphic classes. Delegate to a sister class.cpp
Other Examples 17 For more examples, see the following codes * All downloadable from our site! Shapes.cpp abstract.cpp, virtual.cpp, virt_sel.cpp, vir_err.cpp PassingCar.cpp Delegate to a sister class.cpp