210 likes | 223 Views
Learn about the implementation of dynamic binding, RTTI, virtual functions table, VPTR, and binding times in programming languages. Understand the difference between static and dynamic binding and the importance of RTTI in OOP.
E N D
Implementation of Dynamic Binding • RTTI & Dynamic Binding • Virtual Functions Table • Location of VPTR • Inline and Dynamic Binding?
Binding Time Inclusion Polymorphism + Overriding = Binding Question • Binding: linking between messages and methods • The same entity may refer to objects of different classes, each of which has a different implementation of the same method More generally, binding time is the time when an attribute of some portion of a program, or the meaning of a particular construct is determined • Compile Time • Link Time • Execution Time: • Program Init • Procedure/function begin • Statement Execution • Static Binding (AKA Early Binding): the compiler uses the type of variables to do the binding at a compile (link) time • Dynamic Binding (AKA Late Binding): the decision is made at run time based on the type of the actual values
Reminder: Static vs. Dynamic Binding • Static Binding: binding based on static type. • More efficient • Less flexible • Static type checking leads to safer programs • Dynamic Binding: binding based on dynamic type. • Less efficient • More flexible • May need dynamic type checking!
Run Time Type Information (RTTI) • Always exists in OOP: a prerequisite for dynamic binding • Accessible to programmer? • Not necessarily in statically typed languages • Many things can be done without it! • Almost always in dynamically typed languages • Without it, it is impossible to be sure that an object will recognize a message! • In LST, RTTI is the information accessible from the instance_of pointer
Shapes in C++ class Shape {public:virtualvoid draw(void);virtualvoid hide(void);virtualvoid rotate(int deg); ... protected:int x, y; ... /* Other common fields */}; class Circle: public Shape {public:virtualvoid draw(void) { ... }virtualvoid hide(void) { ... } //...}; class Line: public Shape {public:virtualvoid draw(void) { ... }virtualvoid hide(void) { ... } //...}; class Rectangle: public Shape {public:virtualvoid draw(void) { ... }virtualvoid hide(void) { ... } //...};
RTTI in Smalltalk Obtain class of an object: (x class == Circle) ifTrue: [ ... ] Determine if an object belongs to a class: (x isMemberOf: Shape) ifTrue: [ ... ] Determine if an object belongs to a subclass: (x isKindOf: Shape) ifTrue: [ ... ] Does an object understand a message? (x respondsTo: #draw) ifTrue: [ ... ]
RTTI in Object Pascal Var x: Shape; c: Circle; Begin ... x = c; ... If member(x, Circle) then Begin Writeln('x is a Circle'); x.draw(); { Draw must be defined for class Shape!} { This will call Circle.draw! } endelse Writeln('x is not a circle');
RTTI in C++ class typeinfo {public:virtual ~typeinfo(void);booloperator==(const typeinfo&) const;booloperator!=(const typeinfo&) const; bool before(const typeinfo&) const; const char *name(void) const;private: typeinfo(const typeinfo&); typeinfo& operator= (const typeinfo&); //.. Implementation dependent fields}; No RTTI in early versions of the language. No feature should incur a cost if not used. Even now is very limited class Base { ...}; void f(Base *p){const typeinfo& a = typeid(p); // Type information for Base *const typeinfo& a = typeid(*p); // Actual run time type of *p}
Downcasting vs. Dynamic Binding void draw(Shape *p){ if (Circle *q = dynamic_cast<Circle *>p) { // Draw circle ... } elseif (Line *q = dynamic_cast<Line *>p) { // Draw line ... } elseif (Rectangle *q = dynamic_cast<Rectangle *>p) { // Draw rectangle ... } ... } This pattern is considered harmful: • Order of classes in the if chains is significant • Module must change whenever new class is added to the hierarchy
Programming with Dynamic Binding • Given a polymorphic pointer: Shape* s = ...; • Then, how can we determine if a s is a circle or not? • Dynamic Binding Answer: • The question is wrong!!! • Usually there's no need to determine the dynamic type of an object. • Differences between objects: • Different state • Differences between classes: • Different implementation of methods • Usage of RTTI in all but very special cases indicates a misunderstanding of the power of dynamic binding.
Dynamic Binding in C? void rotate(Shape *p) {switch (p->type) {case rectangle: ... case circle: ... case line: ... ... }} enum Shape_type { rectangle, circle, line, ... }; Coupling (Deja-vu) struct Shape {int x, y; ... /* Other common fields */ Shape_type type;union {struct Rectangle { ... } r; struct Circle { ... } c; struct Line { ... } l; ... /* Other shape kinds */ } data;}; pointers to functions can be used instead. void draw(Shape *p) {switch (p->type) { case rectangle: ... case circle: ... case line: ... ... }}
Disadvantages of the C Solutions • Implicit assumption: consistency between value of the type tag and usage of the union field data. • Programmer’s responsibility: no compiler aid or checking • Dispersed coding: the code for different operations on the same shape is spread all over the program. • Difficult to understand • Insusceptibility to change: whenever a new kind of shape is introduced, the following must be changed: • Definition of Shape_type • Definition of Shape • Function rotate • Function draw • All other functions
Implementation of Virtual Functions Ellipse draw+ hide+ rotate+ Circle rotate++ centre+ class Ellipse { // ... public: virtualvoid draw() const; virtualvoid hide() const; virtualvoid rotate(int); } E1, E2, *P; E1 E2 P class Circle: public Ellipse { //... public: virtual void rotate(int); virtual Point centre(); } C1, C2, C3; C1 C2 C3
The Virtual Methods Table (AKA vtbl) E1 E2 C1 C2 C3 Ellipse VMT Circle VMT draw hide rotate draw hide rotate centre P C++ Jargon:vptr and vtbl Ellipse :: draw Circle :: centre Ellipse :: hide Ellipse :: rotate Circle :: rotate
P->rotate() E1 E2 C1 C2 C3 Ellipse VMT Circle VMT draw hide rotate draw hide rotate centre P Ellipse :: draw Ellipse :: hide Circle :: centre Ellipse :: rotate Circle :: rotate
Location of VPTR #1/2 Borland Style: at the beginning of an object. • Intuitive and simple (usually) • Problematic, if the base class does not have any virtual functions: • When converting a pointer to the derived class into a pointer to the base, the compiler must add an offset to skip over the vptr. • This offset must be subtracted in downcasting. • The compiler must also do a null check, because the offset should not be added in case of a null pointer.
Location of VPTR #2/2 Gnu Style: when first virtual function is encountered • Not so simple or intuitive. • Compiler must have a deterministic algorithm for locating the vptr: • If the function called is not virtual in the static type of the pointer - use static binding • If the function is virtual - add to the pointer the offset corresponding to the size of the most derived “virtual-free” super-class of the static type of the pointer • Casting is so much simpler. • No need to add or subtract any offset in up or down casting.
Dynamic Binding and Efficiency Dynamic binding: compute method address at run time. • There must be a run time penalty in calling a method vs. calling an ordinary routine! • In C++ and other statically typed languages, the penalty is small: • 2-3 pointers references • Few clock cycles • In Smalltalk, CLOS and other dynamically typed languages, the penalty might be larger: • Inheritance hierarchy might change in run-time • Cache and other sophisticated algorithms may help • In Eiffel and other non-separate compilation languages, the penalty might be smaller: • More opportunities for optimization
Inline Virtual Functions? • The real performance hit (up to x 25 ratio) occurs when opportunities for inline are not used. • When can a call to an inline virtual function be made inline? • Not inside function members! void SomeClass::some_function_member(void) { some_virtual_function_member(); } • Virtual calls on a non-reference type (e.g.: by-value variable): void f(SomeClass a) { a.some_virtual_function_member(); } • Explicit callsvoid f(SomeClass& a) { a.SomeClass::some_virtual_function_member(); } • Inside constructors/destructors • C++ employs static binding for this in constructors/destructors • In Java, C# the binding of this is always dynamic
Dispatch Tables • Used in dynamic type systems • Support: • Runtime introduction of new types • Runtime changes to type hierarchy • “Method not found” error messages • Space Efficiency: optimal! • Time Efficiency: lousy; mitigated by a cache of triples: • Class where search started • Selector searched • Address of method found
Binding and Typing in OOP Languages Language Typing Binding ADA CLOS Smalltalk Objective-C Object Pascal Turbo Pascal C++ Eiffel Static Untyped Dynamic Weak/Dynamic Strong Strong Weak/Static Strong/Static Static Dynamic Dynamic Dynamic Dynamic Dynamic Static/Dynamic Dynamic