You are on page 1of 17

Chapter 15

Polymorphism and
Virtual Functions

Copyright © 2017 Pearson Education, Ltd.


All rights reserved.
Virtual Function Basics
• Polymorphism: Associating many meanings to one function
– Virtual functions provide this capability
– Fundamental principle of object-oriented programming!
• Virtual: Existing in "essence" though not in fact
• Virtual Function: Can be "used" before it’s "defined"

Copyright © 2017 Pearson Education, Ltd. All rights reserved. 15-2


Best explained by example
• Assume a base class Figure
– Classes for several kinds of figures can be derived from parent Figure
• Rectangles, circles, ovals, etc.
• Each figure might be an object of different class
– Rectangle data: height, width, center point
– Circle data: center point, radius

• Suppose you want to draw a figure using function: draw()


– Different instructions for each figure
– Each class needs different draw function
Rectangle r;
– Can be called "draw" in each class, so: Circle c;
r.draw(); //Calls Rectangle class’s draw
– Nothing new here yet…
c.draw(); //Calls Circle class’s draw
• Figure may contain functions that apply to "all" figures; consider:
– center(): moves a figure to center of screen
• Erases 1st, then re-draws, so Figure::center() will use function draw() to
re-draw
15-3
– Which draw() function and from which class?
Best explained by example: New Figure
• Consider new kind of figure (Triangle class derived from
Figure)
• Function center() inherited from Figure
– Will it work for triangles?
– It uses draw(), which is different for each figure!
– It will use Figure::draw()  won’t work for triangles
– We want inherited function center()to use function
Triangle::draw() NOT function Figure::draw()
• But class Triangle wasn’t even WRITTEN when Figure::center() was!
– It Doesn’t know "triangles"!

• Answer: Virtual functions. They implement late/dynamic binding


– They tell compiler:
• "Don’t know how function is implemented.”
• “ Wait until used in program then get implementation from object instance“
• Decide which definition to use based on calling object”
– Very important OOP principle! 15-4
Virtual Functions via Example
• You are designing record-keeping program for automotive parts
store
– You want to track sales, but you don’t know all sales yet
– 1st there is only regular retail sales
– However, later you might want to add Discount sales, mail-order sales, etc.
• Depend on other factors besides just price, tax

• Program must:
– Compute daily gross sales (which is sum of all the individual sales bills)
– Calculate largest/smallest sales of day and perhaps average sale for day
• All of these can be calculated from individual bills
– But many functions for computing bills will be added "later"!
• When different types of sales added!

• To accommodate this, function for "computing a bill" will be virtual!


15-5
Class Sale Definition
//This is the header file sale.h.
//This is the interface for the class Sale.
//Sale is a class for simple sales.
#ifndef SALE_H
#define SALE_H
class Sale {
public:
Sale( );
Sale( double thePrice);
double getPrice( ) const;
void setPrice( double newPrice);
virtual double bill( ) const;
double savings(const Sale& other) const;
//Returns the savings if you buy other instead of the calling object.
private:
double price;
};
bool operator < ( const Sale& first, const Sale& second);
//Compares two sales to see which is larger.
#endif // SALE_H

15-6
//This is the file sale.cpp. Implementation for the class Sale.
#include <iostream>

Figures Example: Virtual!


#include "sale.h"
using std::cout;
Sale::Sale( ) : price(0) {}
Sale::Sale( double thePrice) {
if (thePrice >= 0) price = thePrice;
else {
cout << "Error: Cannot have a negative price!\n";
exit(1);
}
}
double Sale::bill( ) const { return price; }
double Sale::getPrice( ) const { return price; }
void Sale::setPrice( double newPrice){
if (newPrice >= 0) price = newPrice;
else {
cout << "Error: Cannot have a negative price!\n";
exit(1);
}
} • Notice BOTH use
double Sale::savings( const Sale &other) const { member function
return (bill( ) - other.bill( )); bill()!
} • We can later define
bool operator < ( const Sale& first, const Sale& second) { derived classes of
return (first.bill( ) < second.bill( )); Sale and define their
} versions of bill()
Class Sale and DiscountSale
• Class Sale: represents sales of single item with no added discounts
or charges.
– "virtual“ reserved keyword in declaration of member function bill
• Impact: Later, derived classes of Sale can define THEIR versions of bill
• Other member functions of Sale will use version based on object of derived class!
• They won’t automatically use Sale’s version!

• Class DiscountSale (next slide): derived from Sale


– DiscountSale’s member function bill() implemented differently than
Sale’s
• Particular to "discounts"
– Member functions savings and "<"
• Will use this definition of bill() for all objects of DiscountSale class!
• Instead of "defaulting" to version defined in Sales class!

15-8
Derived Class DiscountSale Defined
//This is the file discountsale.h.
//This is the interface for the class DiscountSale.
#ifndef DISCOUNTSALE_H
#define DISCOUNTSALE_H
#include "sale.h"
class DiscountSale : public Sale {
public:
DiscountSale( );
DiscountSale( double thePrice, double theDiscount);
//Discount is expressed as a percentage of the price.
//A negative discount is a price increase.
double getDiscount( ) const;
void setDiscount( double newDiscount);
double bill( ) const;
private: • Since bill was declared virtual in the base class, it is
double discount; automatically virtual in the derived class DiscountSale.
}; • You can add the modifier virtual to the declaration of
#endif //DISCOUNTSALE_H bill or omit it as here; in either case bill is virtual
in the class DiscountSale .
• (We prefer to include the word virtual in all virtual
function declarations for readability, even if it is not
required. We omitted it here to illustrate that it is not
required.)
// discountsale.cpp is the implementation for the class DiscountSale.
//The interface for the class DiscountSale is in discountsale.h.
#include "discountsale.h"
discountsale.cpp
DiscountSale::DiscountSale( ) : Sale( ), discount(0) {}
DiscountSale::DiscountSale(double thePrice, double theDiscount)
: Sale(thePrice), discount(theDiscount){}
double DiscountSale::getDiscount( ) const{
return discount;
}
void DiscountSale::setDiscount( double newDiscount){
discount = newDiscount;
}
double DiscountSale::bill( ) const{
double fraction = discount / 100;
return (1 - fraction) * getPrice( );
}

• Qualifier "virtual" does not go in actual function definition


//Demonstrates the performance of the virtual function bill.
#include <iostream>
#include "sale.h" //Not really needed, but safe due to ifndef. Tester1.cpp
#include "discountsale.h"
using std::cout;
using std::endl;
using std::ios;
int main( ){
Sale simple(10.00); //One item at $10.00.
DiscountSale discount(11.00, 10);
//One item at $11.00 with a 10% discount
cout.setf(ios::fixed); Discounted item is cheaper.
cout.setf(ios::showpoint); Savings is $0.10
cout.precision(2);
if (discount < simple){
cout << "Discounted item is cheaper.\n";
cout << "Savings is $" << simple.savings(discount) << endl;
}
else
cout << "Discounted item is not cheaper.\n";
return 0;
}
• Recall class Sale written long before derived class DiscountSale
– savings and "<" compiled before even had ideas of DiscountSale class
• Yet in a call like: simple.savings(discount)
– Call in savings()to bill()knows to use definition of bill()from DiscountSale
• Powerful!
Pure Virtual Functions
• Base class might not have meaningful definition for some of its
members!
• Recall class Figure: All figures are objects of derived classes
– Rectangles, circles, triangles, etc.
– Class Figure has no idea how to draw!
• Make it a pure virtual function: virtual void draw() = 0;
– Pure virtual functions require no definition but Forces all derived classes to
define "their own" version
• abstract base class : Class with one or more pure virtual functions
– Can only be used as base class
– No objects can ever be created from it, since it doesn’t have complete "definitions"
of all it’s members!

• If derived class fails to define all pure’s:


– It’s an abstract base class too 15-12
Extended Type Compatibility Example
//Program to illustrate use of a virtual function to defeat the slicing problem.
#include <string>
#include <iostream>
using namespace std;
class Pet {
public:
string name; // For example purposes only! Not typical!
virtual void print( ) const;
};
class Dog : public Pet {
public:
string breed;
virtual void print( ) const;
};
void Dog::print( ) const {
cout << "name: " << name << endl;
cout << "breed: " << breed << endl;
}
void Pet::print( ) const {
cout << "name: " << name << endl; //Note that no breed is mentioned.
}

15-13
int main( ) {
Dog vdog;
Pet vpet;
vdog.name = "Tiny";
Pets.cpp
vdog.breed = "Great Dane";
vpet = vdog; //Derived objects can be assigned to objects of type Base But NOT the other
way!
cout << "The slicing problem:\n";
//vpet.breed; is illegal since class Pet has no member named breed.
vpet.print( );
cout << "Note that it was print from Pet that was invoked.\n";
cout << "The slicing problem defeated:\n";
The slicing problem:
Pet *ppet; name: Tiny
Dog *pdog; Note that it was print from Pet that was invoked.
pdog = new Dog; The slicing problem defeated:
pdog->name = "Tiny"; name: Tiny
pdog->breed = "Great Dane"; breed: Great Dane
ppet = pdog; name: Tiny
ppet->print( ); breed: Great Dane
pdog->print( );
//The following, which accesses member variables directly
//rather than via virtual functions, would produce an error:
//cout << "name: " << ppet->name << " breed: “ << ppet->breed << endl;
//It generates an error message saying class Pet has no member named breed.
return 0;
} 15-14
Using Classes Pet and Dog and slicing problem
vdog.name = "Tiny";
• Anything that "is a" dog "is a" pet: vdog.breed = "Great Dane";
vpet = vdog;
• Can assign values to parent-types, but not reverse
– A dog is a pet but a pet "is not a" dog (not necessarily)
• Slicing problem
– Notice value assigned to vpet "loses" its breed field!
• cout << vpet.breed; //Produces ERROR msg! slicing problem
• Slicing problem fix
– In C++, slicing problem is nuisance
• It still "is a" Great Dane named Tiny
• We’d like to refer to its breed even if it’s been treated as a Pet
– Can do so with pointers to dynamic variables
• Must use virtual member function: ppet->print();
– Calls print member function in Dog class!
– Because print() is virtual, C++ "waits" to see what object pointer ppet
is actually pointing to before "binding" call 15-15
Virtual Destructors
• Recall: destructors needed to de-allocate dynamically allocated
data
• Consider:
Base *pBase = new Derived;

delete pBase;
– Would call base class destructor even though pointing to Derived class
object!
– Making destructor virtual fixes this!
• Good policy for all destructors to be virtual

15-16
Casting
• Consider: Pet vpet;
Dog vdog;

vdog = static_cast<Dog>(vpet); //ILLEGAL!
– Static downcasting is not allowed: Casting from ancestor type to descendant
– Can’t cast a pet to be a dog,
– but upcasting is OK: From descendant type to ancestor type
vpet = vdog; // Legal!
vpet = static_cast<Pet>(vdog); //Also legal!

• Downcasting allowed with dynamic_cast but it is dangerous and


rarely done

Copyright © 2017 Pearson Education, Ltd. All rights reserved. 15-17

You might also like