Download Understanding Virtual Functions and Polymorphism in OOP and more Study notes Computer science in PDF only on Docsity!
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
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 …
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?
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
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()!
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
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!
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.”
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
“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
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
Type compatibility example
l Consider:
Dog d; Pet p; d.name = "Tiny"; d.breed = "Mutt"; p = d; // “slicing” here
- All okay – a Dog “is a” Pet
l Reverse is not okay
- A Pet might be a Bird, or …
l And p.breed? Nonsense!
l Also see slicing.cpp at
~mikec/cs32/demos/ class Pet { public: // pls excuse bad info hiding string name; virtual void print(); }; class Dog : public Pet { public: string breed; virtual void print(); };
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(d); // like (Pet)d
- But objects sliced if not pointer or reference
l Other way (“downcasting”) is a different story:
d = static_cast(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
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!