Imagine that you are doing some graphics programming, with a variety of shapes to be output to the screen. Initially, you want to support Line, Circle, and Text. Each shape has an X,Y origin and a color.
How might this be done in C++? One way is to use virtual functions. A virtual function is a function member of a class, declared using the "virtual" keyword. A pointer to a derived class object may be assigned to a base class pointer, and a virtual function called through the pointer. If the function is virtual and occurs both in the base class and in derived classes, then the right function will be picked up based on what the base class pointer "really" points at.
For graphics, we can use a base class called Shape, with derived classes named Line, Circle, and Text. Shape and each of the derived classes has a virtual function draw(). We create new objects and point at them using Shape* pointers. But when we call a draw() function, as in:
Shape* p = new Line(0.1, 0.1, Co_blue, 0.4, 0.4); p->draw();
the draw() function for a Line is called, not the draw() function for Shape. This style of programming is very common and goes by names like "polymorphism" and "object-oriented programming". To illustrate it further, here is an example of this type of programming for a graphics application. Annotations in /* */ explain in some detail what is going on.
#include <string.h> #include <assert.h> #include <iostream.h> typedef double Coord; /* The type of X/Y points on the screen. */ enum Color {Co_red, Co_green, Co_blue}; /* Colors. */ // abstract base class for all shape types class Shape { protected: Coord xorig; // X origin Coord yorig; // Y origin Color co; // color /* These are protected so that they can be accessed by derived classes. Private wouldn't allow this. These data members are common to all shape types. */ public: Shape(Coord x, Coord y, Color c) : xorig(x), yorig(y), co(c) {} // constructor /* Constructor to initialize data members common to all shape types. */ virtual ~Shape() {} // virtual destructor /* Destructor for Shape. It's a virtual function. Destructors in derived classes are virtual also because this one is declared so. */ virtual void draw() = 0; // pure virtual draw() function /* Similarly for the draw() function. It's a pure virtual and is not called directly. */ }; // line with X,Y destination class Line : public Shape { /* Line is derived from Shape, and picks up its data members. */ Coord xdest; // X destination Coord ydest; // Y destination /* Additional data members needed only for Lines. */ public: Line(Coord x, Coord y, Color c, Coord xd, Coord yd) : xdest(xd), ydest(yd), Shape(x, y, c) {} // constructor with base initialization /* Construct a Line, calling the Shape constructor as well to initialize data members of the base class. */ ~Line() {cout << "~Line\n";} // virtual destructor /* Destructor. */ void draw() // virtual draw function { cout << "Line" << "("; cout << xorig << ", " << yorig << ", " << int(co); cout << ", " << xdest << ", " << ydest; cout << ")\n"; } /* Draw a line. */ }; // circle with radius class Circle : public Shape { Coord rad; // radius of circle /* Radius of circle. */ public: Circle(Coord x, Coord y, Color c, Coord r) : rad(r), Shape(x, y, c) {} // constructor with base initialization ~Circle() {cout << "~Circle\n";} // virtual destructor void draw() // virtual draw function { cout << "Circle" << "("; cout << xorig << ", " << yorig << ", " << int(co); cout << ", " << rad; cout << ")\n"; } }; // text with characters given class Text : public Shape { char* str; // copy of string public: Text(Coord x, Coord y, Color c, const char* s) : Shape(x, y, c) // constructor with base initialization { str = new char[strlen(s) + 1]; assert(str); strcpy(str, s); /* Copy out text string. Note that this would be done differently if we were taking advantage of some newer C++ features like exceptions and strings. */ } ~Text() {delete [] str; cout << "~Text\n";} // virtual dtor /* Destructor; delete text string. */ void draw() // virtual draw function { cout << "Text" << "("; cout << xorig << ", " << yorig << ", " << int(co); cout << ", " << str; cout << ")\n"; } }; int main() { const int N = 5; int i; Shape* sptrs[N]; /* Pointer to vector of Shape* pointers. Pointers to classes derived from Shape can be assigned to Shape* pointers. */ // initialize set of Shape object pointers sptrs[0] = new Line(0.1, 0.1, Co_blue, 0.4, 0.5); sptrs[1] = new Line(0.3, 0.2, Co_red, 0.9, 0.75); sptrs[2] = new Circle(0.5, 0.5, Co_green, 0.3); sptrs[3] = new Text(0.7, 0.4, Co_blue, "Howdy!"); sptrs[4] = new Circle(0.3, 0.3, Co_red, 0.1); /* Create some shape objects. */ // draw set of shape objects for (i = 0; i < N; i++) sptrs[i]->draw(); /* Draw them using virtual functions to pick up the right draw() function based on the actual object type being pointed at. */ // cleanup for (i = 0; i < N; i++) delete sptrs[i]; /* Clean up the objects using virtual destructors. */ return 0; }
When we run this program, the output is:
Line(0.1, 0.1, 2, 0.4, 0.5) Line(0.3, 0.2, 0, 0.9, 0.75) Circle(0.5, 0.5, 1, 0.3) Text(0.7, 0.4, 2, Howdy!) Circle(0.3, 0.3, 0, 0.1) ~Line ~Line ~Circle ~Text ~Circle
with enum color values represented by small integers.
A few additional comments. Virtual functions typically are implemented by placing a pointer to a jump table in each object instance. This table pointer represents the "real" type of the object, even though the object is being manipulated through a base class pointer.
Because virtual functions usually need to have their function address taken, to store in a table, declaring them inline as the above example does is often a waste of time. They will be laid down as static copies per object file. There are some advanced techniques for optimizing virtual functions, but you can't count on these being available.
Note that we declared the Shape destructor virtual (there are no virtual constructors). If we had not done this, then when we iterated over the vector of Shape* pointers, deleting each object in turn, the destructors for the actual object types derived from Shape would not have been called, and in the case above this would result in a memory leak in the Text class.
Shape is an example of an abstract class, whose purpose is to serve as a base for derived classes that actually do the work. It is not possible to create an actual object instance of Shape, because it contains at least one pure virtual function.
India seo freelance web designer India web development ecommerce website developer India
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100