C++-dynamic-binding4 - The C++ Programming Language...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: The C++ Programming Language Motivation When designing a system it is often the case that developers: Dynamic Binding 1. Know what class interfaces they want, without precisely knowing the most suitable representation 2. Know what algorithms they want, without knowing how particular operations should be implemented Outline Motivation Dynamic vs. Static Binding Shape Example Calling Mechanisms Downcasting Run-Time Type Identi cation Summary In both cases, it is often desirable to defer certain decisions as long as possible Goal: reduce the e ort required to change the implementation once enough information is available to make an informed decision 1 2 Motivation cont'd Motivation cont'd Therefore, it is useful to have some form of abstract place-holder" Information hiding and data abstraction provide compile-time and link-time place-holders  i.e., changes to representations require recompiling and or relinking ::: Dynamic binding provides a dynamic place-holder   i.e., defer certain decisions until run-time without disrupting existing code structure Note, dynamic binding is orthogonal to dynamic linking ::: Dynamic binding is less powerful than pointersto-functions, but more comprehensible and less error-prone i.e., since the compiler performs type checking at compile-time 3 Dynamic binding allows applications to be written by invoking general methods via a base class pointer, e.g., class Base public: virtual int vf void; ; Base *bp = * pointer to a subclass * ; bp- vf ; f g However, at run-time this invocation actually invokes more specialized methods implemented in a derived class, e.g., class Derived : public Base public: virtual int vf void; ; Derived d; bp = &d; bp- vf ; f g invokes Derived::vf In C++, this requires that both the general and specialized methods are virtual functions 4 Dynamic vs. Static Binding Motivation cont'd Dynamic binding facilitates more exible and extensible software architectures, e.g., Not all design decisions need to be known during the initial stages of system development  i.e., they may be postponed until run-time Complete source code is not required to extend the system  i.e., only headers and object code Inheritance review A pointer to a derived class can always be used as a pointer to a base class that was inherited publicly  Caveats: 1. The inverse is not necessarily valid or safe 2. Private base classes have di erent semantics ::: e.g., This aids both exibility and extensibility Flexibility = easily recombine existing components into new con gurations" Extensibility = easily add new components" template class T class Checked Vector : public Vector T ; Checked Vector int cv 20; Vector int *vp = &cv; int elem = *vp 0 ; calls operator int f :::g A question arises here as to which version of operator is called? 5 6 Dynamic vs. Static Binding cont'd Dynamic vs. Static Binding cont'd The answer depends on the type of binding used When to chose use di erent bindings ::: 1. Static Binding: the compiler uses the type of the pointer to perform the binding at compile time. Therefore, Vector::operator will be called Vector::operator vp, 0; 2. Dynamic Binding: the decision is made at runtime based upon the type of the actual object. Checked Vector::operator will be called in this case *vp- vptr 1 vp, 0; Quick quiz: how must class Vector be changed to switch from static to dynamic binding? 7 Static Binding   Use when you are sure that any subsequent derived classes will not want to override this operation dynamically just rede ne hide Use mostly for reuse or to form concrete data types" Dynamic Binding   Use when the derived classes may be able to provide a di erent e.g., more functional, more e cient implementation that should be selected at run-time Used to build dynamic type hierarchies and to form abstract data types" 8 Dynamic Binding in C++ Dynamic vs. Static Binding cont'd In C++, dynamic binding is signaled by explicitly adding the keyword virtual in a method declaration, e.g., E ciency vs. exibility are the primary tradeo s between static and dynamic binding struct Base virtual int vf1 void cout int f1 void; f f "hello n"; g ; Note, virtual functions must be class methods, i.e., they cannot be: g Static binding is generally more e cient since 1. It has less time and space overhead Ordinary stand-alone" functions 2. It also enables function inlining Class data Static methods Dynamic binding is more exible since it enables developers to extend the behavior of a system transparently Other languages e.g., Ei el make dynamic binding the default However, dynamically bound objects are di cult to store in shared memory ::: This is more exible, but may be less e cient 9 10 Dynamic Binding in C++ cont'd Dynamic Binding in C++ cont'd Virtual functions: Virtual functions cont'd: These are methods with a xed calling interface, where the implementation may change in subsequent derived classes, e.g., struct Derived 1 : public Base virtual int vf1 void cout "world n"; ; Supplying the virtual keyword is optional when overriding vf1 in derived classes, e.g., struct Derived 2 : public Derived 1 Still a virtual int vf1 void cout "hello world n"; int f1 void; not virtual ; Note, you can declare a virtual function in any derived class, e.g., struct Derived 3 : public Derived 2 virtual int vf2 int; di erent from vf1! virtual int vf1 int; Be careful!!!! f f g f ::: f g g The virtual function dispatch mechanism uses the dynamic type" of an object identi ed by a reference or pointer to select the appropriate method that is invoked at run-time The selected method will depend on the class of the object being pointed at and not on the pointer type  e.g., void foo Base *bp g bp- vf1 ; f virtual function g f g 11 Base b; Base *bp = &b; bp- vf1 ; prints "hello" Derived 1 d; bp = &d; bp- vf1 ; prints "world" foo &b; prints "hello" foo &d; prints "world" 12 Dynamic Binding in C++ cont'd Shape Example The canonical dynamic binding example: Virtual functions cont'd: Describing a hierarchy of shapes in a graphical user interface library Virtual methods are dynamically bound and dispatched at run-time, using an index into an array of pointers to class methods  e.g., Triangle, Square, Circle, Rectangle, Ellipse, etc. Note, this requires only constant overhead, regardless of the inheritance hierarchy depth :::  A conventional C or Ada solution would The virtual mechanism is set up by the constructors, which may stack several levels deep 1. Use a union or variant record to represent a Shape type ::: e.g., void foo Base *bp 2. Have a type tag in every Shape object f bp- vf1 ; Actual call *bp- vptr 1 bp; 3. Place special case checks in functions that operate on Shapes e.g., functions that implement operations like rotation and drawing g Using virtual functions adds a small amount of time and space overhead to the class object size and method invocation time 14 13 Shape Example cont'd Shape Example cont'd Problems with the conventional approach: C or Ada solution cont'd It is di cult to extend code designed this way: e.g., typedef struct Shape Shape; struct Shape enum  f f CIRCLE, SQUARE, TRIANGLE, RECTANGLE * Extensions go here . * type ; e.g., changes are associated with functions and algorithms  ::: g union struct Circle * . * c ; struct Square * . * s ; struct Triangle * . * t ; struct Rectangle * . * r ;  f f ::: f g ::: f g ::: f ::: g  g u; ; void rotate shape Shape *sp, double degrees switch sp- type  case CIRCLE: return; case SQUARE: Don't forget to break! g  f  :::  g g 15 Therefore, modi cations will occur in portions of the code that switch on the type tag Using a switch statement causes problems, e.g., g f Which are often unstable" elements in a software system design and implementation Setting and checking type tags Falling through to the next case, etc ::: Note, Ei el disallows switch statements to prevent these problems! 16 Shape Example cont'd Shape Example cont'd Problems with the conventional approach cont'd: Data structures are passive"   i.e., functions do most of processing work on di erent kinds of Shapes by explicitly accessing the appropriate elds in the object This lack of information hiding a ects maintainability Solution wastes space by making worst-case assumptions wrt structs and unions Must have source code to extend the system in a portable, maintainable manner An object-oriented solution uses inheritance and dynamic binding to derive speci c shapes e.g., Circle, Square, Rectangle, and Triangle from a general Abstract Base Class ABC called Shape This approach facilities a number of software quality factors: 1. Reuse 2. Transparent extensibility 3. Delaying decisions until run-time 4. Architectural simplicity 17 18 Shape Example cont'd * Abstract Base Class and Derived Classes for Shape * Rectangle Triangle Circle Shape Example cont'd class Shape public: f Shape double x, double y, Color &c : center Point x, y, color c Shape Point &p, Color &c : center p, color c virtual int rotate double degrees = 0; virtual int draw Screen & = 0; virtual ~Shape void = 0; void change color Color &c this- color = c; Point where void const return this- center ; void move Point &to this- center = to; fg Color 1 Point Shape 1 1 fg A 1 f g f f Note, the OOD challenge" is to map arbitrarily complex system architectures into inheritance hierarchies 19 g private: ; Point center ; Color color ; g 20 g Shape Example cont'd Abstract Base Classes ABCs Shape Example cont'd Note, certain methods only make sense on subclasses of class Shape e.g., Shape::rotate and Shape::draw Therefore, class Shape is de ned as an Abstract Base Class Essentially de nes only the class interface Derived i.e., concrete classes may provide multiple, di erent implementations 1. ABCs support the notion of a general concept e.g., Shape of which only more concrete object variants e.g., Circle and Square are actually used 2. ABCs are only used as a base class for subsequent derivations Therefore, it is illegal to create objects of ABCs However, it is legal to declare pointers or references to such objects  ::: ABCs force de nitions in subsequent derived classes for unde ned methods In C++, an ABC is created by de ning a class with at least one pure virtual function" Compare with deferred classes in Ei el ::: 22 21 Shape Example cont'd Shape Example cont'd Pure virtual functions Side note regarding pure virtual destructors Pure virtual functions must be methods They are de ned in the base class of the inheritance hierarchy, and are often never intended to be invoked directly  i.e., they are simply there to tie the inheritance hierarchy together by reserving a slot in the virtual table ::: Therefore, C++ allows users to specify pure virtual functions"   Using the pure virtual speci er = 0 indicates methods that are not meant to be de ned in that class Note, pure virtual functions are automatically inherited ::: 23 The only e ect of declaring a pure virtual destructor is to cause the class being de ned to be an ABC Destructors are not inherited, therefore:  A pure virtual destructor in a base class will not force derived classes to be ABCs  Nor will any derived class be forced to declare a destructor Furthermore, you will have to provide a de nition i.e., write the code for a method for the pure virtual destructor in the base class  Otherwise you will get run-time errors! 24 Shape Example cont'd The C++ solution to the Shapes example uses inheritance and dynamic binding In C++, the special case code is associated with the derived class data structures e.g., class Circle : public Shape public: Circle Point &p, double rad; virtual void draw Screen &; virtual void rotate double degrees private: double radius ; ; class Rectangle : public Shape public: Rectangle Point &p, double l, double w; virtual void rotate double degrees; virtual void draw Screen &; private: double length , width ; f fg ::: Shape Example cont'd C++ solution cont'd Using the special relationship between base classes and derived subclasses, any Shape * can now be rotated" without worrying about what kind of Shape it points to The syntax for doing this is: void rotate shape Shape *sp, double degrees sp- rotate degrees; *sp- vptr 1  sp, degrees; g f g Note, we are still interface compatible" with original C version! ::: ; g 25 Shape Example cont'd Shape Example cont'd vtable Circle 0 Rotate Characteristics of the C++ dynamic binding solution: vtable Rectangle Draw 0 vptr Draw Associate all specializations with the derived class Rather than with function rotate shape  This makes it possible to add new types derived from base class Shape without breaking existing code i.e., most extensions changes occur in only one place vptr Circle Rotate 26 Rectangle  This code will continue to work regardless of what derived class of Shape that sp actually points to, e.g., Circle c; Rectangle r; e.g., add a new class Square derived from class Rectangle: class Square : public Rectangle Inherits length and width from Rectangle public: Square Point &p, double base; virtual void draw Screen &; virtual void rotate double degree if degree  90.0 != 0 Reuse existing code Rectangle::rotate degree; * .* ; f f rotate shape &c, 100.0; rotate shape &r, 250.0; g ::: g 27 28 f Shape Example cont'd Shape Example cont'd C++ solution with dynamic binding cont'd Comparison between 2 approaches If support for Square was added in the C or Ada solution, then every place where the type tag was accessed would have to be modi ed We can still rotate any Shape object by using the original function, i.e., void rotate shape Shape *sp, double degrees f  sp- rotate degrees; i.e., modi cations are spread out all over the place  g Square s; Circle c; Rectangle r; Including both header les and functions Note, the C or Ada approach prevents extensibility if the provider of Square does not have access to the source code of function rotate shape! rotate shape &s, 100.0; rotate shape &r, 250.0; rotate shape &c, 17.0; i.e., only the header les and object code is required to allow extensibility in C++ 30 29 Shape Example cont'd Shape Example cont'd Comparison between 2 approaches cont'd * C solution * void rotate shape Shape *sp, double degree switch sp- type  case CIRCLE: return; case SQUARE: if degree  90 == 0 return; Example function that rotates size shapes by angle degrees: void rotate all Shape *vec , int size, double angle for int i = 0; i size; i++ f f vec i - rotate angle; f else * FALLTHROUGH * ; g vec i - rotate angle tion call is a virtual func- It is resolved at run-time according to the actual type of object pointed to by vec i case RECTANGLE: break; ::: i.e., g vec i - rotate angle becomes *vec i - vptr 1  vec i , angle; g 31 32 Shape Example cont'd Sample usage of function rotate all is Shape Example cont'd vtable Circle Shape *shapes = new Circle  * . * , new Square  * . *  ; int size = sizeof shapes sizeof *shapes; rotate all shapes, size, 98.6; vtable Square f ::: 0 Rotate Draw 0 Rotate Draw ::: g vptr Circle Note, it is not generally possible to know the exact type of elements in variable shapes until run-time However, at compile-time we know they are all derived subtypes of base class Shape  This is why C++ is not fully polymorphic, but is strongly typed vptr Square shapes 0 1 Here's what the memory layout looks like 33 34 Calling Mechanisms Shape Example cont'd Note that both the inheritance dynamic binding and union switch statement approaches provide mechanisms for handling the design and implementation of variants The appropriate choice of techniques often depends on whether the class interface is stable or not Adding a new subclass is easy via inheritance, but di cult using union switch since code is spread out everywhere On the other hand, adding a new function to an inheritance hierarchy is di cult, but relatively easier using union switch since the code for the function is localized Given a pointer to a class object e.g., class Foo *ptr how is the method call ptr- f arg resolved? There are three basic approaches: 1. Static Binding 2. Virtual Function Tables 3. Method Dispatch Tables C++ and Java use both static binding and virtual function tables. Smalltalk and Objective C use method dispatch tables Note, type checking is orthogonal to binding time ::: 35 36 Calling Mechanisms cont'd Static Binding Calling Mechanisms cont'd Virtual Function Tables Method f is converted into an index into a table of pointers to functions i.e., the virtual function table that permit run-time resolution of the calling address Method f's address is determined at compile link time Provides for strong type checking, completely checkable resolvable at compile time  Main advantage: the most e cient scheme  The *ptr object keeps track of its type via a hidden pointer vptr to its associated virtual function table vtable Virtual functions provide an exact speci cation of the type signature e.g., it permits inline function expansion Main disadvantage: the least exible scheme  The user is guaranteed that only operations speci ed in class declarations will be accepted by the compiler 38 37 Calling Mechanisms cont'd Calling Mechanisms cont'd f1 Virtual Function Tables cont'd vptr Main advantages f2 1 2 vtable 0 obj 1 1. More exible than static binding 2. There only a constant amount of overhead compared with method dispatching  e.g., in C++, pointers to functions are stored in a separate table, not in the object! Main disadvantages  Less e cient  vptr vptr obj 2 obj 3 e.g., class Foo public: virtual int f1 void; virtual int f2 void; int f3 void; private: f e.g., often not possible to inline the virtual function calls ::: data ; Foo obj 1, obj 2, obj 3; ::: g 39 40 Downcasting Calling Mechanisms cont'd Method Dispatch Tables Method f is looked up in a table that is created and managed dynamically at run-time i.e., add delete change methods dynamically Main advantage: the most exible scheme i.e., new methods can be added or deleted on-the- y and allows users to invoke any method for any object Main disadvantage: generally ine cient and not always type-secure May require searching multiple tables at runtime Some form of caching is often used Performing run-time type checking along with run-time method invocation further decreases run-time e ciency Type errors may not manifest themselves until run-time        Downcasting is de ned as: Either manually or automatically casting a pointer or reference of a base class type to a type of a pointer or reference to a derived class. i.e., going the opposite direction from usual base-class derived-class" inheritance relationships Downcasting is useful for 1. Cloning an object e.g., required for deep copies" 2. Restoring an object from disk This is hard to do transparently ::: 3. Taking an object out of a heterogeneous collection of objects and restoring its original type Also hard to do, unless the only access is via the interface of the base class 42 41 Downcasting cont'd Contravariance Downcasting can lead to trouble due to contravariance  Downcasting cont'd It is consequence of inheritance that works against programmers in a symmetrically opposing fashion to the way inheritance works for them Consider the following derivation hierarchy: struct Base int i ; virtual int foo void return this- i ; ; struct Derived : public Base int j ; virtual int foo void return this- j ; ; void foo void dp b i bp d i f f g ? j g f f g g f Base b; Derived d; Base *bp = &d; OK, a Derived is a Base Derived *dp = &b; Error, a Base is not necessarily a Derived Problem: what happens if dp- j is referenced or set? g 43 44 ::: Downcasting cont'd Downcasting cont'd Contravariance cont'd Since a Derived object always contains a Base part certain operations are well de ned: bp = &d; bp- i = 10; bp- foo ; calls Derived::foo ; However, since base objects do not contain the data portions of any of their derived classes, other operations are not de ned  e.g., this assignment accesses information beyond the end of object b: dp = Derived * &b; dp- j = 20; big trouble! Note, C++ permits contravariance if the programmer explicitly provides a downcast, e.g., dp = Derived * &b; unchecked cast It is the programmer's responsibility to make sure that operations upon dp don't access nonexistent elds or methods 45 Traditionally, downcasting was necessary due to the fact that C++ originally did not support overloading on function return" type e.g., in C++ the following is currently not allowed in most compilers: struct Base virtual Base *clone void; ; struct Derived : public Base virtual Derived *clone void; Error! ; However, assuming we make the appropriate virtual Base *clone void change in class Derived Base *ob1 = new Derived; Derived *ob2 = new Derived; The following are syntax errors" though they are actually type-secure: Derived *ob3 = ob1- clone ; error Derived *ob4 = ob2- clone ; error To perform the intended operation, we must use a cast to trick" the type system, e.g., Derived *ob5 = Derived * ob1- clone ; f g f g ::: 46 Downcasting cont'd The right way to handle this is to use the C++ Run-Time Type Identi cation RTTI feature However, since most C++ compilers do not support type-safe downcasting, some workarounds include: 1. Don't do it, since it is potentially non-type-safe 2. Use an explicit cast e.g., ob5 and cross your ngers 3. Encode type tag and write massive switch statements Which defeats the purpose of dynamic binding Run-Time Type Identi cation RTTI is a technique that allows applications to use the C++ run-time system to query the type of an object at run-time Only supports very simple queries regarding the interface supported by a type RTTI is only fully supported for dynamicallybound classes Alternative approaches would incur unacceptable run-time costs and storage layout compatibility problems 4. Manually encode the return type into the method name: Derived *ob6 = ob2- cloneDerived ; 47 48 Run-Time Type Identi cation cont'd RTTI could be used in our original example involving ob1 Base *ob1 = new Derived; if Derived *ob2 = dynamic cast Derived * ob1- clone  * use ob2 * ; else * error! * For a dynamic cast to succeed, the actual type" of ob1 would have to either be a Derived object or some subclass of Derived If the types do not match the operation fails at run-time Run-Time Type Identi cation cont'd dynamic cast used with references A reference dynamic cast that fails throws a bad cast exception e.g., void clone Base &ob1 try f f If failure occurs, there are several ways to dynamically indicate this to the application: To return a NULL pointer for failure * To throw an exception e.g., in the case of reference casts  dynamic cast Derived & ob1; ::: * g catch bad cast   Derived &ob2 = f * ::: * g ::: g 49 50 Run-Time Type Identi cation cont'd Run-Time Type Identi cation cont'd Along with the dynamic cast extension, the C++ language now contains a typeid operator that allows queries of a limited amount of type information at run-time Here are some short examples Includes both dynamically-bound and non-dynamicallybound types ::: e.g., Base *bp = new Derived; Base &br = *bp; typeid bp == typeid Base * true typeid bp == typeid Derived * false typeid bp == typeid Base false typeid bp == typeid Derived false typeid *bp == typeid Derived true typeid *bp == typeid Base false typeid type name const Type info & typeid expression const Type info & ! ! Note that the expression form returns the run-time type of the expression if the class is dynamically bound ::: 51 typeid br == typeid Derived true typeid br == typeid Base false typeid &br == typeid Base * true typeid &br == typeid Derived * false 52 Run-Time Type Identi cation cont'd A common gripe is RTTI will encourage the dreaded switch statement of death," e.g., void foo Object *op op- do something ; if Foobar *fbp = dynamic cast Foobar * op fbp- do foobar things ; else if Foo *fp = dynamic cast Foo * op fp- do foo things ; else if Bar *bp = dynamic cast Bar * op bp- do bar things ; else f g op- do object stu ; Summary Dynamic binding enables applications and developers to defer certain implementation decisions until run-time i.e., which implementation is used for a particular interface It also facilitates a decentralized architecture that promotes exibility and extensibility e.g., it is possible to modify functionality without modifying existing code Implementing this style of type tagging by hand rather than by the compiler leads to an alternative, slower method of dispatching methods There is some additional run-time overhead from using dynamic binding i.e., duplicating the work of vtables in an unsafe manner that a compiler cannot double check However, alternative solutions also incur overhead However, even an automated approach can be hard to make e cient!  53 ::: e.g., the union switch approach 54 ...
View Full Document

This note was uploaded on 08/08/2011 for the course CS 101 taught by Professor Jitenderkumarchhabra during the Summer '11 term at National Institute of Technology, Calicut.

Ask a homework question - tutors are online