Class Sale Represents sales of single item with no added discounts or charges. Notice reserved word "virtual" in declaration of member function bill Impact: Later, derived classes of Sale can define THEIR own versions of function bill Other member functions of Sale will use version based on the object instance of derived class! They won t automatically use Sale s version! ch 15-18
Derived Class DiscountSale Defined class DiscountSale : public Sale public: DiscountSale(); DiscountSale( double theprice, double the Discount); double getdiscount() const; void setdiscount(double newdiscount); virtual double bill() const; private: double discount; ; ch 15-19
DiscountSale s Implementation of bill() double DiscountSale::bill() const double fraction = discount/100; return (1 fraction)*getprice(); Qualifier "virtual" does not go in actual function definition "Automatically" virtual in derived class Declaration (in interface) not required to have "virtual" keyword either (but usually does) ch 15-20
DiscountSale s Implementation of bill() Virtual function in base class: "Automatically" virtual in derived class Derived class declaration (in interface) Not required to have "virtual" keyword But typically included anyway, for readability ch 15-21
Derived Class DiscountSale DiscountSale s member function bill() may be implemented differently than base class 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! ch 15-22
Virtual: Wow! Recall class Sale written long before derived class DiscountSale Members savings and "<" compiled before even had ideas of a DiscountSale class Yet in a call like: DiscountSale d1, d2; Sale *ps; ps = & d1; d1->savings(d2); Call in savings() to function bill() knows to use definition of bill() from DiscountSale class Powerful! ch 15-23
Late binding Virtual: How? Virtual functions implement late binding Tells compiler to "wait" until function is used in program Decide which definition to use based on calling object Very important OOP principle! ch 15-24
Overriding Virtual function definition changed in a derived class We say it has been "overidden" Similar to redefined Recall: for standard functions So: Virtual functions changed: overridden Non-virtual functions changed: redefined ch 15-25
Virtual Functions: Why Not All? Clear advantages to virtual functions as we ve seen Late binding easy to manage the derived classes from a (same) parent class One major disadvantage: overhead! Uses more storage Late binding is "on the fly", so programs run slower So if virtual functions not needed, should not be used ch 15-26
Pure Virtual Functions Base class might not have "meaningful" definition for some of its members! Its purpose is solely for others to derive from 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; ch 15-27
Abstract Base Classes Pure virtual functions require no definition (implementation) Forces all derived classes to define "their own" version Class with one or more pure virtual functions is: abstract base class Can only be used as base class No objects can ever be created (instantiated) from it Since it doesn t have complete "definitions" of all its members! If derived class fails to define all pure s: It s an abstract base class too ch 15-28
Extended Type Compatibility Given: Derived is a derived class of Base Derived objects can be assigned to objects of type Base But NOT the other way! (Base class object is not a derived class object) (e.g., when animal is base class and dog and cat are derived classes, animal_object is not always a dog_object!) Consider previous example: A DiscountSale "is a" Sale, but reverse not true ( Sale is not always DiscountSale!) ch 15-29
Extended Type Compatibility Example class Pet public: string name; virtual void print() const; ; class Dog : public Pet public: string breed; virtual void print() const; ; Pet Dog ch 15-30
Class Pet and Class Dog Now given declarations: Dog vdog; Pet vpet; Notice member variables name and breed are public! For example purposes only! Not typical! ch 15-31
Using Classes Pet and Dog Anything that "is a" dog "is a" pet: vdog.name = "Tiny"; vdog.breed = "Great Dane"; vpet = vdog; These are allowable Can assign values to parent-types, but not reverse A pet "is not always a" dog (not necessarily) ch 15-32
Slicing Problem Notice value assigned to vpet "loses" its breed field! cout << vpet.breed; Produces ERROR msg! Called slicing problem Might seem appropriate Dog was moved to Pet variable, so it should be treated like a Pet And therefore pet does not have "dog" properties Makes for interesting philosphical debate ch 15-33
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 (GreatDane) even if it has been treated as a Pet Can do so with pointers to dynamic variables ch 15-34
Slicing Problem Example Pet *ppet; Dog *pdog; pdog = new Dog; pdog->name = "Tiny"; pdog->breed = "Great Dane"; ppet = pdog; Cannot access breed field of object pointed to by ppet: cout << ppet->breed; //ILLEGAL! ch 15-35
Slicing Problem Example Must use virtual member function: ppet->print(); Calls print() member function in Dog class! Because it s virtual C++ "waits" to see what object pointer ppet is actually pointing to before "binding" call ch 15-36
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 ch 15-37
Casting Consider: Pet vpet; Dog vdog; vdog = static_cast<dog>(vpet); //ILLEGAL! // Pet does not have all attributes defined in Dog! Can t cast a pet to be a dog, but: vpet = vdog; // Legal! vpet = static_cast<pet>(vdog); //Also legal! Upcasting is OK From descendant type to ancestor type ch 15-38
Downcasting Downcasting is dangerous! Casting from ancestor type to descended type Assumes information is "added" Can be done with dynamic_cast: Pet *ppet; ppet = new Dog; Dog *pdog = dynamic_cast<dog*>(ppet); Legal, but dangerous! Dog에는 Pet에정의되어있지않은정보도포함되어있으므로, down casting에서는일부정보가정의되지않은채로남아있게된다. ( 예 : Dog에는 breed 정보가정의되어있지만, Pet에는 breed 정보가없으므로, 위 assignment에서는 breed 정보가정의되어있지않은상태로남아있게된다.) Downcasting rarely done due to pitfalls Must track all information to be added All member functions must be virtual ch 15-39
Inner Workings of Virtual Functions Don t need to know how to use it! Principle of information hiding Virtual function table Compiler creates it Has pointers for each virtual member function Points to location of correct code for that function Objects of such classes also have pointer Points to virtual function table ch 15-40
Example of Inheritance with Virtual Function Inheritance Hierarchy Shape Circle Triangle Rectangle Sphere Prism Cube ch 15-41
/*** Shape.h */ #ifndef SHAPE_H #define SHAPE_H #include <string> using namespace std; class Shape friend ostream& operator<<(ostream &, Shape &); public: Shape(int pos_x, int pos_y, string cl); // constructor virtual ~Shape(); virtual double area() = 0; // pure virtual function (to be late bound) virtual double volume() = 0; // pure virtual function (to be late bound) virtual void print(ostream& fout) = 0; // pure virtual function (to be late bound) protected: int pos_x; // position x int pos_y; // position y string color; ; #endif ch 15-42
/** Shape.cpp */ #include <iostream> #include "Shape.h" using namespace std; Shape::Shape(int x, int y, string cl) :pos_x(x), pos_y(y), color(cl) cout << " Constructor (Shape). n"; Shape::~Shape() cout << " Destructor (Shape). n"; void Shape::print(ostream& fout) fout << "Shape: pos( << pos_x <<, << pos_y; fout << ), color ( << color << ")"; ostream& operator<<(ostream &ostr, Shape &shp) ostr << "Shape (" << shp.pos_x << ", ; ostr << shp.pos_y << ", << shp.color << ")"; return ostr; ch 15-43
/*** Circle.h */ #ifndef CIRCLE_H #define CIRCLE_H #include <string> #include "Shape.h" using namespace std; class Circle : public Shape friend ostream& operator<<(ostream &ostr, Circle &cir); public: Circle(int x, int y, int r, string clr); Circle(Circle &cir); // copy constructor virtual ~Circle(); double area(); double volume() return 0.0; void print(ostream& fout); int getradius(); void setradius(int r); Circle& operator++(); Circle& operator++(int dummy); protected: int radius; ; #endif ch 15-44
/** Circle.cpp (1) */ #include <iostream> #include "Circle.h" using namespace std; #define PI 3.14159 Circle::Circle(int x, int y, int r, string clr) :Shape(x, y, clr), radius(r) cout << " Constructor (Circle). n"; return; Circle::Circle(Circle & cir) :Shape(cir.pos_x, cir.pos_y, cir.color), radius (cir.radius) cout << " Copy constructor (Circle). n"; return; double Circle::area() double area; area = radius * radius * PI; return area; ch 15-45
/** Circle.cpp (2) */ void Circle::print(ostream& fout) fout << "Circle: pos(" << pos_x << ", " << pos_y << "), radius (" ; fout << radius << "), color(" << color << ), area(" << area() << ")"; int Circle::getRadius() return radius; void Circle::setRadius(int r) radius = r; Circle::~Circle() cout << " Destructor (Circle). n"; return; ostream& operator<<(ostream &ostr, Circle &cir) ostr << "Circle (radius:" << cir.getradius() << ", area:" << cir.area() << ")"; return ostr; ch 15-46
/** Circle.cpp (3) */ Circle& Circle::operator++() radius = radius + 1; return (*this); Circle& Circle::operator++(int dummy) Circle *pcir; pcir = new Circle(*this); radius = radius + 1; return *pcir; ch 15-47
/** Sphere.h */ #ifndef SPHERE_H #define SPHERE_H #include <string> #include "Circle.h" using namespace std; class Sphere : public Circle friend ostream& operator<<(ostream &ostr, Sphere &sph); public: Sphere(int x, int y, int r, string clr); Sphere(Sphere& sph); virtual ~Sphere(); double area(); double volume(); void print(ostream& fout); Sphere& operator++(); Sphere& operator++(int dummy); private: ; #endif ch 15-48
/** Sphere.cpp (1) */ #include <iostream> #include "Sphere.h" #include "Circle.h" using namespace std; #define PI 3.14159 Sphere::Sphere(int x, int y, int r, string clr) :Circle(x, y, r, clr) cout << " Constructor (Sphere). n"; Sphere::Sphere(Sphere& cyl) :Circle(cyl.pos_x, cyl.pos_y, cyl.radius, cyl.color) cout << " Copy constructor (Sphere). n"; Sphere::~Sphere() cout << " Destructor (Sphere). n"; double Sphere::area() double a = 0.0; a = 4.0*PI*radius*radius; return a; ch 15-49
/** Sphere.cpp (2) */ double Sphere::volume() double v = 0.0; v = (4.0*PI*radius*radius*radius)/3.0; return v; void Sphere::print(ostream& fout) fout << "Sphere: pos(" << pos_x << ", " << pos_y << "), radius (" << radius; fout << "), color(" << color << ), area(" << area() << "), volume << volume() << ) ; ostream& operator<<(ostream &ostr, Sphere &sph) ostr << "Sphere(radius:" << sph.getradius(); ostr << ", area:" << sph.area() << ", volume:" << sph.volume() << ")"; return ostr; ch 15-50
/** Sphere.cpp (3) */ Sphere& Sphere::operator++() // prefix increment radius++; return (*this); Sphere& Sphere::operator++(int dummy) // postfix increment Sphere *pcyl; pcyl = new Sphere(*this); radius++; return (*pcyl); ch 15-51
/** main.cpp (1) */ #include <iostream> #include <string> #include <fstream> #include "Shape.h" #include "Circle.h" #include "Square.h" #include "Sphere.h" #include "Cube.h" using namespace std; #define NUM_SHAPES 4 int main() ofstream fout; Shape *pshapes[num_shapes]; Circle *pcir; Sphere *psph; Square *psq; Cube *pcb; fout.open("output.txt"); if(fout.fail()) cout <<"Output.txt file opening failed n"; exit (1); ch 15-52
/** main.cpp (2) */ cout << "Creating Circle: n"; pcir = new Circle(1, 2, 3, "RED"); fout << *pcir << endl; cout << "Creating Square: n"; psq = new Square(1, 2, 3, "BLUE"); fout << *psq << endl; cout << "Creating Sphere: n"; psph = new Sphere(1, 2, 3, "YELLOW"); fout << *psph << endl; cout << "Creating Cube: n"; pcb = new Cube(1, 2, 3, "CYAN"); fout << *pcb << endl; pshapes[0] = pcir; pshapes[1] = psq; pshapes[2] = psph; pshapes[3] = pcb; for (int i=0; i<num_shapes; i++) fout << "pshapes[" << i <<"]->print(): "; pshapes[i]->print(fout); fout << endl; ch 15-53
/** main.cpp (3) */ fout << endl; fout << *psph << endl; fout << "prefix increment of Sphere: n"; cout << "prefix increment of Sphere: n"; fout << ++(*psph) << endl; fout << "postfix increment of Sphere: n"; cout << "postfix increment of Sphere: n"; (*psph)++; fout << *psph << endl; fout << endl; fout << *pcb << endl; fout << "prefix decrement of Cube: n"; cout << "prefix decrement of Cube: n"; fout << --(*pcb) << endl; fout << "postfix decrement of Cube: n"; cout << "postfix decrement of Cube: n"; (*pcb)--; fout << *pcb << endl ; cout << "Deleting Sphere: n" ; delete psph; cout << "Deleting Cube: n" ; delete pcb; // end of main() fout << endl << endl; fout.close(); ch 15-54
Execution Result (1) output.txt (2) Console output ch 15-55
Summary 15-1 Late binding delays decision of which member function is called until runtime In C++, virtual functions use late binding Pure virtual functions have no definition Classes with at least one are abstract No objects can be created from abstract class Used strictly as base for others to derive ch 15-56
Summary 15-2 Derived class objects can be assigned to base class objects Base class members are lost; slicing problem Pointer assignments and dynamic objects Allow "fix" to slicing problem Make all destructors virtual Good programming practice Ensures memory correctly de-allocated ch 15-57
Homework 15 15.1 C++ programming with Inheritance, polymorphism and virtual function: shape, point, square, circle, and regular triangle Class Shape contains public member methods: virtual print(); virtual area(); By default, method area() returns 0.0 as result. Class Point inherits from class Shape, contains protected data members of double x; double y; and initializes x = 0.0 and y = 0.0 at default constructor. Class Rectangle inherits from class Point, contains protected data member double width, and initializes width = 0.0 at default constructor. Class Circle inherits from class Point, contains protected data member double radius, and initializes radius = 0.0 at default constructor. Class Triangle inherits from class Point, contains protected data members double side, double height, and initializes side = 0.0 and height = 0.0 at default constructor. Class Square, Circle and Triang have member method double area() const; to calculate the area of each shape, and print() to print ShapeName. Polymorphism should be provided using virtual function print() at Class Shape and Class Point that is dynamically binding into the print() method of Class Square, Circle and Triang. ch 15-58
main()...... Shape *shape[3]; Rectangle rec(1,2,3); Circle cl(4); Triangle tri(5,6) shape[0] = &rec; shape[1] = &cl; shape[2] = &tri; for (int i = 0; i< 3; i++) arr_shape[i] -> print(); cout << area is : << arr_shape[i] -> area() << endl;.... 15.2 Programming Projects 15-1 15.3 Programming Projects 15-2 ch 15-59