virtual functions concepts
play

Virtual functions concepts l Virtual: exists in essence though not - PowerPoint PPT Presentation

Virtual functions concepts l Virtual: exists in essence though not in fact l Idea is that a virtual function can be used before it is defined And it might be defined many, many ways! l Relates to OOP concept of polymorphism


  1. Virtual functions – concepts l Virtual: exists in essence though not in fact l Idea is that a virtual function can be “ used ” before it is defined – And it might be defined many, many ways! l Relates to OOP concept of polymorphism – Associate many meanings to one function l Implemented by dynamic binding – A.k.a. late binding – happens at run-time

  2. Polymorphism example: figures l Imagine classes for several kinds of figures – Rectangles, circles, and ovals (to start) – All derive from one base class: Figure l All “ Figure ” objects inherit: void draw() – Of course, each one implements it differently! Rectangle r; Circle c; r.draw(); // Calls Rectangle class ’ s draw() c.draw(); // Calls Circle class ’ s draw l Nothing new here yet …

  3. Figures example cont. – center() l Consider that base class Figure has functions that apply to “ all ” figures l e.g., center() : moves figure to screen center – Erases existing drawing, then re-draws the figure – So Figure::center() uses draw() to re-draw l But which draw() function will be used? – We ’ re implementing base class center() function, so we have to use the base class draw() function. Right? l Actually, it turns out the answer depends on how draw() is handled in the base class

  4. Poor solution: base works hard l Figure class tries to implement draw to work for all (known) figures – First devise a way to identify a figure ’ s “ type ” – Then Figure::draw() uses conditional logic: if ( /* the Figure is a Rectangle */ ) Rectangle::draw(); else if ( /* the Figure is a Circle */ ) Circle::draw(); ... l But what if a new kind of figure comes along? – e.g., how to handle a derived class Triangle ?

  5. Better solution: virtual function l Base class declares that the function is virtual: virtual void draw() const; l Remember it means draw() exists in essence l Such a declaration tells compiler “ I don’t know how this function is implemented, so wait until it is used in a program, and then get its implementation from the object instance .” l The instance will exist in fact (eventually) – Therefore, so will the implementation at that time! l Function “ binding ” happens late – dynamically

  6. Another virtual function example (Sale, DiscountSale, Display 15.11) l Record-keeping system for auto parts store – Track sales, compute daily gross, other stats – All based on data from individual bills of sale l Problem: lots of different types of bills l Idea – start with a very general Sale class that has a virtual bill() function: virtual double bill() const; l Rest of idea – many different types of sales will be added later, and each type will have its own version of the bill() function

  7. Sale functions: savings and op < double Sale::savings(const Sale &other) const { return (bill() – other.bill()); } bool operator < (const Sale &first, const Sale &second) { return (first.bill() < second.bill()); } l Notice both functions use member function bill() !

  8. A class derived from Sale class DiscountSale : public Sale { public: DiscountSale(); DiscountSale(double price, double discount); double getDiscount() const; void setDiscount(double newDiscount); double bill() const; // implicitly virtual private: double discount; // inherits price };

  9. DiscountSale ’ s bill() function l First note – it is automatically virtual – Inherited trait, applies to any descendants – Also note – rude not to declare it explicitly l Of course, definition never says virtual: double DiscountSale::bill() const { double fraction = discount/100; return (1 – fraction)*getPrice(); } – Must use access method as price is private

  10. The power of virtual is actual! l e.g., base class Sale written long before derived class DiscountSale l Sale had members savings and ‘ < ’ before there was any idea of class DiscountSale l Yet consider what the following code does DiscountSale d1, d2; d1.savings(d2); // calls Sale ’ s savings function l In turn, class Sale ’ s savings function uses class DiscountSale ’ s bill function. Wow!

  11. Clarifying some terminology l Recall that overloading ≠ redefining l Now a new term – overriding means redefining a virtual function l Polymorphism is an OOP concept – Overriding gives many meanings to one name l Dynamic binding is what makes it all work l “ Thus, ” as Savitch puts it, “ polymorphism, late binding, and virtual functions are really all the same topic. ”

  12. Why not all virtual functions? l Philosophy issue: pure OOP vs. efficiency – All functions are virtual by default in another popular programming language (Java) – there must take steps to make functions non-virtual – C++ default is non-virtual – programmer must explicitly declare (except when inherited trait) l Virtual functions have more “ overhead ” – More storage – for class virtual function table – Slower – a look-up step; less optimization

  13. Simpler polymorphism demo (~mikec/cs32/demos/figures) l Base: Figure has virtual void print() – print() is used in printAt(lines) l Derived: Rectangle just overrides print() l Which print() is used in the following code? Figure *ptr = new Rectangle, &ref = *new Rectangle('Q', 5, 10, 4); ptr->printAt(1); ref.printAt(1); l What if print() was not declared virtual ? l What if line 2 above just had ref , not &ref ? – To know why, see “slicing” … a few slides from now

  14. “Pure virtual” and abstract classes l Actually class Figure ’ s print() function is useless – It should have been a pure virtual function: virtual void draw() const = 0; – Says not defined in this class – means any derived class must define its own version, or be abstract itself l A class with one or more pure virtual functions is an abstract class – so it can only be a base class – An actual instance would be an incomplete object – So any instance must be a derived class instance

  15. A sorting hierarchy See …/demos/sorting

  16. Types when inheritance is involved l Consider: void func (Sale &x) {…} or similarly: void func (Sale *xp) {…} – What type of object is x (or *xp), really? Is it a Sale? – Or is it a DiscountSale, or even a CrazyDiscountSale? l Just Sale members are available – But might be virtual, and Sale might even be abstract – & and * variables allow polymorphism to occur l Contrast: void func (Sale y) {…} – What type of object is y? It ’ s a Sale. Period. – Derived parts are “ sliced ” off by Sale ’ s copy ctor – Also in this case, Sale cannot be an abstract class

  17. Type compatibility example l Consider: class Pet { Dog d; Pet p; public: // pls excuse bad info hiding d.name = "Tiny"; string name; d.breed = "Mutt"; virtual void print(); p = d; // “ slicing ” here }; – All okay – a Dog “ is a ” Pet l Reverse is not okay class Dog : public Pet { – A Pet might be a Bird, or … public: l And p.breed ? Nonsense! string breed; l Also see slicing.cpp at virtual void print(); ~mikec/cs32/demos/ };

  18. Destructors should be virtual l Especially if class has virtual functions l Derived classes might allocate resources via a base class reference or pointer: Base *ptrBase = new Derived; ... // a redefined function allocates resources delete ptrBase; l If dtor not virtual, derived dtor is not run! l If dtor is virtual – okay: run derived dtor, immediately followed by base dtor

  19. Casting and inherited types l Consider again: Dog d; Pet p; l “ Upcasting ” (descendent to ancestor) is legal: p = d; // implicitly casting “ up ” p = static_cast<Pet>(d); // like (Pet)d – But objects sliced if not pointer or reference l Other way ( “ downcasting ” ) is a different story: d = static_cast<Dog>(p); // ILLEGAL – Can only do by pointer and dynamic cast : Pet *pptr = new Dog; // we know it’s a Dog Dog *dptr = dynamic_cast<Dog*>(pptr) – But can be dangerous, and is rarely done

  20. Multiple inheritance and virtual l Idea: a ClockRadio is a Radio and an AlarmClock – But what if class Radio and class AlarmClock are both derived from another class, say Appliance ? – Doesn ’ t each derived object contain an Appliance portion? – So wouldn ’ t a Clockradio have two copies of that portion, and how can such a scheme possibly work properly? l Answer: it can work, but only by using virtual inheritance! class Radio : virtual public Appliance; class AlarmClock : virtual public Appliance; class ClockRadio : public Radio, public AlarmClock; – Now a Clockradio has just one Appliance portion, not two l See demo code in ~mikec/cs32/demos/multi-inherit l But note: hierarchy is still messed up, and still lots of chances for ambiguity – best to avoid multi-inheritance!

  21. How do virtual functions work? l Not exactly magic, but safe to consider it so l virtual tells compiler to “ wait for instructions ” until the function is used in a program l So the compiler creates a virtual function table for the class, with pointers to all virtual functions l In turn, every object of such a class will be made to store a pointer to its own class ’ s virtual function table – try …/demos/sizeofvirtual.cpp l At runtime: follow the pointers to find the code!

Recommend


More recommend