Understanding Virtual Functions and Polymorphism in OOP, Study notes of Computer science

The concept of virtual functions in Object-Oriented Programming (OOP), focusing on their relationship with polymorphism and dynamic binding. Virtual functions allow a base class to 'not know' how a derived class implements a function, enabling the use of the base class's functions with derived class objects. examples of virtual functions in the context of figures and sales, illustrating their importance and benefits.

Typology: Study notes

2021/2022

Uploaded on 09/27/2022

ronyx
ronyx 🇬🇧

4

(4)

213 documents

1 / 21

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Virtual functions – concepts
lVirtual: exists in essence though not in fact
lIdea is that a virtual function can be
used before it is defined
And it might be defined many, many ways!
lRelates to OOP concept of polymorphism
Associate many meanings to one function
lImplemented by dynamic binding
A.k.a. late binding – happens at run-time
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15

Partial preview of the text

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!