Lecture 5 Inheritance Polymorphism and the Object Memory

Lecture 5 Inheritance, Polymorphism and the Object Memory Model 1

Process Memory Layout • Each process consists of three distinct areas: • The Heap - stores values allocated using the new operator • The Stack - stores values in activation frames • The Code Segment - stores the code of all the classes used in the program executed by the process • The object (an instance of a class) uses memory: • Variables created using new are stored on the heap • This is done during run time. While the code is being executed! • Variables created without using new are stored on the stack • This is done during compile time. • The compiler provides an offset from Stack. Pointer (SP) for each variable • The compiler provides a contiguous block of memory for each variable

Object Memory Model • Object are instance of a class that contains: • State: data members • static data members are allocated once and shared among all class instances • non-static data members are not shared • Methods: member functions • Methods are shared amongst all instances of the same class • Their code is stored once in memory for each class • They can access the data members of the calling object only • Object is implemented at runtime as a region of storage in memory • a contiguous block of memory • object is basically a struct with methods

Object Memory Layout • C++ has no standard way to storing variables in memory! • It means each compiler may store data differently • It means that same struct may have different sizes using different compilers • G++7. 3. 0 basic storage methodology: variable type can only reside in a memory location divisible by its size • • • Chars can be stored in any memory address Shorts can reside in memory addresses divisible by two Integers/floats can reside in memory addresses divisible by four Double can resize in memory addresses divisible by eight And so on. • Memory layout example at the website uses a different compiler • Stores variables at addresses divisible by word size regardless of their type

Memory layout example 5

Field Alignment • fields c 1 and c 2 are "word aligned" within the block of memory of the object: • fields start on a word boundary (word=4 B) • memory left "wasted" • compiler flag not to align fields • aligning fields - easy data accessing 6

sizeof() • sizeof() - size used by a given type. • computed at compile-time, compiler operator • return size allocated for object data-types • sizeof(A) = 20 (5 words of 4 bytes). 7

8

• reference to a field - compiler uses offset of field within the object • The above situation is translated to: • push activation frame for new block with one variable of 20 bytes (for a 1) • invoke constructor of A on the address of register S (top of stack) • READ [S]+12, B - address [S]+12 into register B 9

Memory Layout and Inheritance • class B extends class A • fields defined in A exist in B • new fields for objects of type B. • block memory for objects of class B is larger than that of objects of class A. 10

• first 20 bytes = structure of type A. • "look at a B value“ as if an "A value": • take first part of B and "cut" to sizeof(A). 11

The Code Segment • It contains code of all the classes used in the program and executed by the process • Method is stored in code region allocated in the process in which the class is used • It is encoded as sequence of processor instructions • It is known to compiler by start address in memory

Method Execution • Executing a method: • compiler maintains a table where it keeps track of the address of each method of the class • compiler invokes a method of a class • method has access to internal state of object, wherever it may be! • Method Invocation: • parameters are pushed on stack • method invoked by using CALL instruction • The parameter of CALL is the address of the first instruction of the method

The Implicit this Parameter • How method knows where are fields of object? • the compiler passes a "hidden" parameter to method call • The parameter contains the address of the object • The parameter is called this Example: this->foo() is called like this: foo(this) • Note: • static functions do not implicitly send this, as a result, the programmer has no access to the object. • can be invoked without an instance: C: : static_method(x)

Polymorphism: use an operator or function in different ways. • Different meanings to the operators or functions (poly = many / morph = shape) • 6+5 • “a”+”bc” • 3. 2+4. 75 • Polymorphism = essential property of OO languages 15

Static and Dynamic Function Binding • They are also called Early and Late Binding • To convert function calls into addresses • These addresses are jumped to during code execution • Static Binding • Done by the compiler to directly associate the function with an address during compile time! • Since all functions have unique addresses • The compiler replaces a function call with a machine language instruction. • This instruction tells the CPU to jump to the address of the function once executed

• Example: • Early binding 17

Early binding • Main: Output: 18

Early binding • Main: Output: 19

Late binding • Using virtual functions Output: 20

Example: virtual functions 21

Example: virtual functions 22

s->draw() • Compiler does not know the address of the function to invoke • Same C++ instruction will sometimes execute: • "call [Circle: : draw]" • "call [Rectangle: : draw]" • How does the compiler manage to produce the right code? 23

Dynamic Binding • In this case, the compiler matches the function call with the correct function definition at runtime • The compiler identifies the type of object at runtime • Then matches the function call with the correct function definition • By default, early binding takes place • Explicit commands are used to ensure applying dynamic binding

Virtual functions (vtable mechanism) • Late Binding: compiler delaying the decision of method to invoke to runtime, instead of compile time. • This can be achieved by declaring a virtual function • The actual method invoked depends on the type of the value of the object at runtime not on the type of the value at compile time • The compiler creates virtual table during compilation • Assists the compiler in applying dynamic binding properly 25

• invoke s->draw(): • Call draw() of Rectangle or Circle by value to which s is bound at time of invocation 26

Virtual table (vtable) • how can we know which code to invoke when we call a method through an object’s pointer? • The value of object in memory is extended by a pointer to a table with function address • The table is stored explicitly in the code region. • There’s a table for each class that contains virtual methods. 27

vtable for class foo • The block of memory of a value of type foo starts with a pointer vptr to the vtable of class foo - followed by the state of the object. 28

Invoking a virtual method foo *d; d->m(): • dereferencing d's vptr, • looking up the m entry in the vtable, • dereferencing that pointer to call the correct method code. 29

![*((*d)[2])(d); ? ? • Assume vptr is the first element in d: • d *((*d)[2])(d); ? ? • Assume vptr is the first element in d: • d](http://slidetodoc.com/presentation_image_h2/7a33f1c2225f54f170af3b2d23588ddf/image-31.jpg)
*((*d)[2])(d); ? ? • Assume vptr is the first element in d: • d is the address of the beginning of the block of memory which stores the foo value bound to d • *d is the content of the first word in the block of memory: it contains the address of the vtable • (*d)[2] is the address of the 3 rd element in the vtable (the address of method m) • *((*d)[2])(d) - invoke function located at third slot in the vtable of foo and pass to it the address of the value 31

Inheritance and vtable • When a class extends another class, how is the vtable managed? • bar extends foo. bar overrides m, and introduces 2 new methods s and t. 32

33

• The compiler generates a new vtable for class bar. vtable elements point: • to same addresses as parent when method is not overridden. • To overridden methods or to new methods otherwise. • vtable of inherited class is an extension of the vtable of the parent table: • shared methods appear in the same order. • new methods in the child class appear at the end of the vtable. 34

Virtual Table Single Inheritance Base Class Example • The virtual destructor is assigned slot 0 • Pure virtual function mult() is assigned slot 1 • There is no mult() definition, so the address of the library function pure_virtual_called() is placed within the slot • If that instance should get invoked, generally, it would terminate the program! • y() is assigned to slot 2 • z() is assigned to slot 3 • What slot is x() assigned? • None because it is not declared to be virtual class Point { public: virtual ~Point(); virtual Point& mult( float ) = 0; //. . . other functions. . . float x() const { return _x; } virtual float y() const { return 0; } virtual float z() const { return 0; } //. . . protected: Point( float x = 0. 0 ); float _x; };

Virtual Table Single Inheritance Derived Class Example • The Point 2 d destructor is assigned to slot 0 • replacing Point’s destructor address • The Point 2 d mult() instance is assigned to slot 1 • replacing the pure virtual instance • The Point 2 d y() instance is assigned to slot 2 • replacing Point’s y() address • Slot 3 does not change • It retains Point's inherited instance of z() • Although not declared virtual they appear in the vtable because of Point. • It is customary to keep the overrided functions virtual. class Point 2 d : public Point { public: Point 2 d( float x = 0. 0, float y = 0. 0 ) : Point( x ), _y( y ) {} ~Point 2 d(); // overridden base class virtual functions Point 2 d& mult( float ); float y() const { return _y; } //. . . other functions. . . protected: float _y; };

Virtual Table Single Inheritance 2 nd Derived Class Example • This is similar to first derived class mechanics • Point 3 d's destructor assigned to slot 0 • replacing Point 2 d’s destructor address • Point 3 d's instance of mult() assigned to slot 1 • replacing Point’s mult() address • It places Point 2 d's inherited instance of y() in slot 2 • Point 3 d's address of z() in slot 3 • So how is ptr->z()invoked? • We don’t know the type of ptr • We know that z() is at slot 3 • We know that the vtable starts at 0 • Thus: (ptr[0][3])(); • Regardless of ptr’s type! class Point 3 d: public Point 2 d { public: Point 3 d( float x = 0. 0, float y = 0. 0, float z = 0. 0 ) : Point 2 d( x, y ), _z( z ) {} ~Point 3 d(); // overridden base class virtual functions Point 3 d& mult( float ); float z() const { return _z; } //. . . other functions. . . protected: float _z; };

Vtable and Multiple Inheritance • multiple inheritance: a class can extend more than one base class 38

Multiple Inheritance • Class student inherits both from class person and gp_list_node • vtable layout becomes more complex in such a situation. 39

Multiple Inheritance • object of type student has 3 types of fields 1. inherited from person 2. inherited from gp_list_node 3. declared in class student • 3 types of methods (inherited from person, gp_list_node, defined in class student) 40

Vtable • vptr points to student specific vtable • vtable first contains person methods, next methods that appear in class student • The object then contains the person fields. (look at a student value as a person value - just ignore the bottom part of the block) we cannot store the gp_list_node’s fields at the beginning of the block. 41

• So where do we store gp_list_node fields? • Right after the person fields! • But before them we store the vptr of gp_list_node. • Finally we store the student specific data members at the end of the data block 42

Look-at 43

Passing ‘this’ to member functions • how to pass valid this pointer to a method of gp_list_node that is not overridden? • code cannot know that what it receives as the this pointer is not a real gp_list_node. • accesses the fields of the value it has, assuming it is a gp_list_node value. 44

pointer fixup – look down • compiler knows what type of method is invoked - either inherited from person, gp_list_node or specific to student. • If inherited from gp_list_node: pass this as "corrected address" (this+d, where d=sizeof(person)). • look down from this address, block memory looks as a valid gp_list_node object 45

Casting and Addresses • when we cast an object to a different class, we may end up with a different address in memory Student c; • We get that: (void*)&c != (void*)static_cast<gp_list_node*>(&c) 46

Multiple Inheritance: Code Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Base 1 { public: Base 1(); virtual ~Base 1(); virtual void speak. Clearly(); virtual Base 1 *clone() const; protected: float data_Base 1; }; class Base 2 { public: Base 2(); virtual ~Base 2(); virtual void mumble(); virtual Base 2 *clone() const; protected: float data_Base 2; }; class Derived : public Base 1, public Base 2 { public: Derived(); virtual ~Derived(); virtual Derived *clone() const; protected: float data_Derived; };

Multiple Inheritance: Function Call Ambiguity • What happens in these cases: • Derived, Base 1 and Base 2 classes have clone method implementation • Base 1 and Base 2 classes have clone method implementation but Derived does not have clone method implementation • Which clone implementation is executed for these three cases for each two code implementations mentioned above? 1 #include <iostream> 2 3 int main(){ 4 Derived *d = new Derived; 5 d->clone(); 6 delete d; 7} 1 #include <iostream> 2 3 int main(){ 4 Base 1 *d = new Derived; 5 d->clone(); 6 delete d; 7} • Which case out of the six is problematic? 1 #include <iostream> 2 3 int main(){ 4 Base 2 *d = new Derived; 5 d->clone(); 6 delete d; 7}

Code Example: Base 1 Class Implementation 1 #include <iostream> 2 3 class Base 1 { 4 public: 5 Base 1(){ 6 std: : cout <<"Base 1 constructor" << std: : endl; 7 } 8 Base 1(float data_Base 1): data_Base 1(data_Base 1){} 9 virtual ~Base 1(){ std: : cout <<"Base 1 destructor" << std: : endl; } 10 virtual void speak. Clearly(){ std: : cout << "speak!" << std: : endl; }; 11 virtual Base 1 *clone() const{ 12 std: : cout << "Base 1 clone" << std: : endl; 13 return new Base 1(data_Base 1); 14 } 15 protected: 16 float data_Base 1; 17 };

Code Example: Base 2 Class Implementation 1 #include <iostream> 2 3 class Base 2 { 4 public: 5 Base 2(){ 6 std: : cout <<"Base 2 constructor" << std: : endl; 7 } 8 Base 2(float data_Base 2): data_Base 2(data_Base 2){} 9 virtual ~Base 2(){ std: : cout <<"Base 2 destructor" << std: : endl; } 10 virtual void mumble(){ std: : cout << "mumble" << std: : endl; } 11 virtual Base 2 *clone() const{ 12 std: : cout << "Base 2 clone" << std: : endl; 13 return (new Base 2(data_Base 2)); 14 } 15 protected: 16 float data_Base 2; 17 };

Code Example: Derived Class Implementation 1 #include <iostream> 2 3 class Derived : public Base 1, public Base 2 { 4 public: 5 Derived(){ 6 std: : cout <<"Derived constructor" << std: : endl; 7 } 8 Derived(float data_Derived): data_Derived(data_Derived){} 9 virtual ~Derived(){ std: : cout <<"Derived destructor" << std: : endl; } 10 virtual Derived *clone() const{ 11 std: : cout << "Derived clone" << std: : endl; 12 return new Derived(data_Derived); 13 } 14 protected: 15 float data_Derived; 16 };

Multiple Inheritance: Function Call Ambiguity • What happens in this case: • Derived, Base 1 and Base 2 classes have clone method implementation • Base 1 and Base 2 classes have clone method implementation but Derived does not have clone method implementation • What is the output for these three cases for each two code implementations mentioned above: 1 #include <iostream> 2 3 int main(){ 4 Derived *d = new Derived; 5 d->clone(); 6 delete d; 7} 1 #include <iostream> 2 3 int main(){ 4 Base 1 *d = new Derived; 5 d->clone(); 6 delete d; 7} 1 #include <iostream> 2 3 int main(){ 4 Base 2 *d = new Derived; 5 d->clone(); 6 delete d; 7}

Casting • Implicit conversion: • short a=2000; int b; b=a; • automatically performed when a value is copied to a compatible type • Explicit conversion • short a=2000; int b; b = (int) a; • explicit type-casting allows to convert any pointer into any other pointer type • static_cast<> : casting at compile time. 53

Dynamic cast • Dynamic_cast: • Can be used only with pointers and references. • Used when casting from base to derived. • Ensures at run-time that the result of the type conversion is a valid object. • always successful when we cast a class to one of its base classes. 54

55

Dynamic cast • Dynamic_cast: • Returns nullptr if the casting fails for pointers, or throws exception when casting fails for references. • dynamic_cast<> requires the Run-Time Type Information (RTTI) to track the dynamic types. • This imposes a runtime overhead. 56

dynamic_cast Example #include <iostream> struct A { virtual void f() { cout << "Class A" << endl; } }; struct B : A { virtual void f() { cout << "Class B" << endl; } }; struct C : A { virtual void f() { cout << "Class C" << endl; } }; void f(A* arg) { B* bp = dynamic_cast<B*>(arg); C* cp = dynamic_cast<C*>(arg); if (bp) bp->f(); else if (cp) cp->f(); else arg->f(); }; int main() { A aobj; C cobj; A* ap = &cobj; A* ap 2 = &aobj; f(ap); // line 17 f(ap 2); // line 18 } • Output? • Line 17: Class C • Line 18: Class A

g++ fdump -class-hierarchy option • look at the exact structure of the vtables the compiler generates for us • g++ has an option that allows us to get this information in a readable manner: • g++ c. cpp -fdump-class-hierarchy -o c • generates a text file called c. cpp. t 01. class which gives the details of the memory layout and vtable of the classes defined in the file. 58

59

60

More on virtual functions and tables • https: //en. wikipedia. org/wiki/Virtual_function • https: //www. learncpp. com/cpp-tutorial/125 -the-virtual-table/ 61

Virtual methods: performance issues • invoking a virtual method is more expensive at runtime than invoking a regular function. 1. 2 operations: get the location of the function's code from vtable, and invoke the function. 2. Object values are extended by one word for each vtable to which they refer. 3. Virtual methods cannot be compiled "inline" • Inline: avoids calling a function (pushing arguments on the stack, popping them out at end). The compiler copies the code of the function at invocation point. • Advantage: no parameter passing and returning protocol. • Disadvantage: Executable is long… 62

• 3 costs combined can have a strong effect on performance of program. • in C++, methods are not virtual by default. If a class does not have virtual method (or is derived from a class with ones), then does not include a vtable. • in Java, methods are always virtual. 63

Example: pure virtual functions 64

pure virtual functions • Shape cannot be instantiated. • Shape is called an abstract class (like an “interface”). • If we did not implement draw() in Circle, then Circle would also be abstract. • Optional feature: • Pure virtual functions may have an implementation, which can only be called from derived classes. 65

Implementing Interfaces in C++ • Java avoids the complexity of multiple inheritance • restricts programmers to single inheritance and the mechanism of interfaces. • Interfaces = restricted method of multiple inheritance. • interfaces in C++ = pure virtual class 66

pure virtual abstract class • does not define any data members. • All of its methods are virtual. • All of its methods are abstract (marked as virtual m() = 0; ) 67

"virtual inheritance“ = "implement an interface" • avoid the problem of ambiguous hierarchy composition – diamond problem • inheritance=arranging classes in memory 68

Multiple Inheritance: The Diamond Problem 1 class Top{}; 2 class Left : public Top{}; 3 class Right : public Top{}; 4 class Bottom : public Left, public Right{ }; 5 6 int main(){ 7 Left* lptr = new Bottom(); 8 Right* rptr = new Bottom(); 9 Top* tptr = new Bottom(); 10 Bottom* bptr = new Bottom(); 11 } • What’s the error at line 9 during compilation? • error: ‘Top’ is an ambiguous base of ‘Bottom’ • Why? • Bottom will contains two copies of the sub-object Top! • Which one to use when we access Top data members? Ambiguous!

The Diamond Problem Solution: Virtual Inheritance 1 2 class Top{}; 3 4 class Left : virtual public Top{}; 5 6 class Right : virtual public Top{}; 7 8 class Bottom : public Left, public Right{ }; 9 10 int main(){ 11 Left* lptr = new Bottom(); Right* rptr = new Bottom(); Top* tptr = new Bottom(); Bottom* bptr = new Bottom(); } • Virtual inheritance ensures that one copy of Top is added to Bottom • Done by prefixing the inheritance syntax with the virtual keyword

The diamond problem • Consider the following class hierarchy 71

The diamond problem • A call to bat. eat() is ambiguous because there are two Animal (indirect) base classes 72

The diamond problem • Furthermore: Bat b; Animal &a = b; error: which Animal subobject should a Bat cast into, a Mammal: : Animal or a Winged. Animal: : Animal? *One can use static casting, for example: static_cast<Mammal&>(bat). eat() 73

Better solution: virtual inheritance The Animal portion of Bat: : Winged. Animal is now the same Animal instance as the one used by Bat: : Mammal. Bat: : eat() is not ambiguous. 74

The Visitor Pattern • when you want to decide at runtime which piece of logic to execute=polymorphism introduce polymorphism • Not in line with OO. Requires changes when adding another type. 75

Visitor pattern - re-organization of code • Example: • We have printer objects and document objects • a printer can print documents. • code to print each document is different in printer. • Problem: • Using virtual tables/functions we determine the type of printer, but not document. 76

Goal • each printer type has a separate method for handling each document type. • We handle the printer type by vtable. • We do not want each document to know about each printer – bad coupling. • We don’t want to ask “instance. Of()”. • Goal: also determine the document type using vtable mechanism. 77

How? “double dispatch” • use the virtual table mechanism to invoke the print() method on the right printer, with a base document* as parameter. • A base document will have a virtual print. Me() method. • Each document’s print. Me() knows its type and calls back the printer’s print(). 78

How? “double dispatch” • a printer object receives the print message with a document object: 1. first dispatch happens when we select the appropriate printer object using print() 2. second dispatch is achieved by sending the print. Me() message to the document. 79

80

81

82

83

Output dispatching function <print> called PDFDoc accepting a print call printing a PDF doc dispatching function <print> called Doc accepting a print call printing a Doc doc using bad. Print printing a PDF doc printing a Doc doc 84
- Slides: 84