1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual...
-
Upload
damon-hutchinson -
Category
Documents
-
view
213 -
download
0
Transcript of 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual...
1
159.234159.234 LECTURE 17LECTURE 17
More on Inheritance, Virtual More on Inheritance, Virtual Inheritance, & Virtual Inheritance, & Virtual
Destructors, Destructors,
1717
2
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
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;}
};
Let’s consider the following inheritance hierarchy:Let’s consider the following inheritance hierarchy:11
Parent Ctor and Dtor.cpp
3
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent 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,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.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 After X::X() ctor finishes, control is returned to Y::Y(). As soon as Y::Y() finishes, Z::Z() gains control and finishes last.Y::Y() finishes, Z::Z() gains control and finishes last.
Parent Ctor and Dtor.cpp
4
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
Parent default constructors execute in top-down order.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
5
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
22
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;
};
Let’s consider the following inheritance hierarchy:Let’s consider the following inheritance hierarchy:
Parent Ctor and Dtor - Dtor calls.cpp
6
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
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;};
Class derived from PersonClass derived from Person
Parent Ctor and Dtor - Dtor calls.cpp
7
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
int main(){ Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; }
The main function:The main function:
Parent Ctor and Dtor - Dtor calls.cpp
8
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
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
int main(){ Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; }
9
More on InheritanceMore on InheritanceParent destructors execute in a bottom-up order.Parent destructors execute in a bottom-up order.**
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
int main(){ Person x("Scratchy"); { Student y("Itchy","Biology") ; } return 0; }
10
More on InheritanceMore on InheritanceParent Constructors and DestructorsParent Constructors and Destructors
In an inheritance hierarchy, each constructor In an inheritance hierarchy, each constructor invokes its parent constructor invokes its parent constructor beforebefore executing executing itself, and each destructor invokes its parent itself, and each destructor invokes its parent destructor destructor afterafter executing itself. executing itself.
1717
Summary:Summary:
11
More on InheritanceMore on InheritanceVirtual DestructorsVirtual Destructors
Virtual functions Virtual functions are are overridden by functions that have the by functions that have the same ‘signature’ and are defined in same ‘signature’ and are defined in derived classesderived classes..
1717
Since the names of constructors and destructors are Since the names of constructors and destructors are unique for each class, it leads us to think that they cannot unique for each class, it leads us to think that they cannot be declared virtual.be declared virtual.
That is only true for constructors. Constructors are always That is only true for constructors. Constructors are always unique for each class. unique for each class.
On the other hand, On the other hand, destructorsdestructors could be made could be made virtualvirtual..
12
More on InheritanceMore on InheritanceMemory LeakMemory Leak
Consider the following class named X:Consider the following class named X:
1717
class X{
public: X() { p = new int[2]; cout << "X(). "; } ~X() { delete [] p; cout << "~X()." << endl;} private: int *p;
};
Virtual Destructor - Memory Leak.cpp
13
Virtual DestructorVirtual DestructorMemory LeakMemory Leak
And another class named Y, derived from X:And another class named Y, derived from X:
1717
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
14
Virtual DestructorVirtual DestructorMemory LeakMemory Leak
Each iteration creates a new instance of YEach iteration creates a new instance of Y
1717
int main(){
for(int i=0; i < 8; ++i){
X *r = new Y;delete r;
}return 0;
}
Where is the memory leak here?Where is the memory leak here?
This loop would invokethe base class’s constructor,as well as Y’s constructor, Allocating 4100 bytes (4 bytesfor each int)
Note: 1023*4 + 2*4 = 4100 bytes
Virtual Destructor - Memory Leak.cpp
15
Virtual DestructorVirtual DestructorMemory LeakMemory Leak
Let’s see the program in action.Let’s see the program in action.
1717
int main(){
for(int i=0; i < 8; ++i){
X *r = new Y;delete r;
}return 0;
}
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!
Virtual Destructor - Memory Leak.cpp
16
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().
Virtual DestructorVirtual DestructorProgram OutputProgram Output
1717
Only the X destructor is invoked!So, how can we invoke the Y destructor?
Virtual Destructor - Memory Leak.cpp
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.
17
Virtual DestructorVirtual DestructorTo plug this memory leak,To plug this memory leak,
1717
Change the base class’s destructor into a virtual function.
class X{ public: X() { p = new int[2]; cout << "X(). "; } virtual ~X(){ delete [] p; cout << "~X()." << endl; }
private: int *p;
};
Rule of thumb:Declare the base destructor virtual whenever your class hierarchy uses dynamic dynamic bindingbinding!
Virtual Destructor - Memory Leak Resolved.cpp
18
Virtual DestructorVirtual DestructorProgram OutputProgram Output
1717
Each iteration of the loop calls both destructors, restoring all memory that was allocated by the new operator.
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().
Virtual Destructor - Memory Leak Resolved.cpp
19
More on InheritanceMore on Inheritance““Dreaded Diamond” of Class InheritanceDreaded Diamond” of Class Inheritance
1717
Base
Derived1 Derived2
Join
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 ambiguityIn converting from Join* to Base*, or from Join& to Base&.
20
More on InheritanceMore on Inheritance““Dreaded Diamond” of Class InheritanceDreaded Diamond” of Class Inheritance
1717
Base
Derived1 Derived2
Join
class Base{public:protected: int data;};
class Derived1 : public Base {..}class Derived2 : public Base {..}
class Join : public Derived1, public Derived2{ public: void method(){ data = 1; }}
int main(){ Join* j = new Join(); Base* b = j; return 0;}
ambiguous
DreadedDiamond.cpp
21
More on InheritanceMore on Inheritance““Dreaded Diamond” of Class InheritanceDreaded Diamond” of Class Inheritance
1717
Base
Derived1 Derived2
Join
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.
22
More on InheritanceMore on Inheritance““Dreaded Diamond” of Class InheritanceDreaded Diamond” of Class Inheritance
1717
Base
Derived1 Derived2
Join
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.
Use the virtual keywordvirtual keyword in the inheritance partinheritance part of the classes that derive directly from the top of the diamond.
virtual virtual
23
More on InheritanceMore on Inheritance““Dreaded Diamond” of Class InheritanceDreaded Diamond” of Class Inheritance
1717
class Base{public:protected: int data;};
class Derived1 : public virtualvirtual Base {..}class Derived2 : public virtualvirtual Base {..}
class Join : public Derived1, public Derived2{ public: void method(){ data = 1; }}
int main(){ Join* j=new Join(); Base* b=j; return 0;}
good!
Therefore, an instance of Join will have only a single Base subobject.
This eliminates ambiguities.
DreadedDiamond.cpp
24
More on InheritanceMore on Inheritance““Delegate to a sister class” via virtual inheritanceDelegate to a sister class” via virtual inheritance
1717
class Base{ public: virtual void foo() = 0; virtual void bar() = 0; };
class Derived1 : public virtual Base{public: virtual void foo();
};
class Derived2: public virtual Base{public: virtual void bar() { cout << "void Derived2::bar()" << endl;}
};
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;
}
?
How does Derived1 know anything about bar()?
void Derived1::foo() { bar(); bar(); }
Delegate to a sister class.cpp
25
More on InheritanceMore on Inheritance““Delegate to a sister class” via virtual inheritanceDelegate to a sister class” via virtual inheritance
1717
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 delegationcross delegation’ can be a powerful technique for customizing the behaviour of polymorphic classes.
Delegate to a sister class.cpp
26
Other ExamplesOther Examples
Shapes.cppShapes.cpp
**
1717
For more examples, see the following codesFor more examples, see the following codes
abstract.cpp, virtual.cpp, virt_sel.cpp, vir_err.cppabstract.cpp, virtual.cpp, virt_sel.cpp, vir_err.cpp
PassingCar.cppPassingCar.cpp
All downloadable from our site!
Delegate to a sister class.cpp