26
1 159.234 159.234 LECTURE 17 LECTURE 17 More on Inheritance, More on Inheritance, Virtual Inheritance, & Virtual Inheritance, & Virtual Destructors, Virtual Destructors, 17 17

1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

Embed Size (px)

Citation preview

Page 1: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

1

159.234159.234 LECTURE 17LECTURE 17

More on Inheritance, Virtual More on Inheritance, Virtual Inheritance, & Virtual Inheritance, & Virtual

Destructors, Destructors,

1717

Page 2: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 3: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 4: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 5: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 6: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 7: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 8: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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; }

Page 9: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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; }

Page 10: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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:

Page 11: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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..

Page 12: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 13: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 14: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 15: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 16: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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.

Page 17: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 18: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 19: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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&.

Page 20: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 21: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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.

Page 22: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 23: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 24: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 25: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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

Page 26: 1 159.234LECTURE 17 159.234 LECTURE 17 More on Inheritance, Virtual Inheritance, & Virtual Destructors, 17

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