class Parent { public: int i; Parent(); void print(); }; Parent::Parent() { i = 1; } void Parent::print() { cout << i << endl; } | class Child: public Parent { public: string s; Child(); void print(); }; Child::Child() { s = "s"; } void Child::print() { cout << s << ";"; Parent::print(); } |
Then the following is perfectly legal, and simply copies any
inherited fields (in this case just i) from c into p. (Note that trying the reverse, c = p;, won't compile.) |
|
Similarly, the following is perfectly legal, but the swap operates only using the Parent class fields/methods:
void swap(Parent &x, Parent &y) { Parent tmp = x; x = y; y = tmp; x.print(); y.print(); } int main() { Parent p; Child c; swap(p, c); } |
There are a number of practical forms of polymorphism in C++:
Example 1: the result of (a/b) is an int if a and b are both ints, but is a float if either of them are floats.
Example 2: a+b performs addition for numeric values, but concatenation if a and b are strings.
The programmer can then call the function from elsewhere in the program, and the compiler recognizes from the parameters which real datatype needs to be substituted for the placeholder name, and builds/compiles an appropriate version of that function with the correct data types.
Example 1: the compiler recognizes that a string version of swap needs to be compiled for the call from main, so it completes and compiles a string version of the templated function.
// note the syntax used to tell the compiler that the following // function is just a template, and that SwapType is the // placeholder name we'll be using for the datatype template <class SwapType> void swap(SwapType &x, SwapType &y) { SwapType tmp = x; x = y; y = tmp; } int main() { string m = "m"; string n = "n"; swap(m, n); // the 'real' call with strings passed to x and y, // so the compiler writes and compiles a string version // of swap } |
template <class DType> class MyArray { private: DType *arr; int size; public: MyArray(); ~MyArray(); void fill(DType val); }; int main() { // create an instance of the class, specifying we want // to use int in place of DType throughout the class MyArray<int> MyIntArray; MyIntArray.fill(0); } template <class DType> MyArray<DType>::MyArray() { size = 10; arr = new DType [size]; } template <class DType> MyArray<DType>::~MyArray() { delete [] arr; } template <class DType> void MyArray<DType>::fill(DType val) { for (int i = 0; i < size; i++) arr[i] = val; } |
Ordinarily if you call a method through an object
the object type determines the method type, e.g.
triangle t;
t.print(); // calls triangle::print on t
The same principle applies for dynamically allocated objects:
triangle *ptr = new triangle();
ptr->print(); // calls triangle::print on ptr's triangle
Suppose we want to have a generic Shape variable, but during the run of a program choose a specific flavour of shape (e.g. from derived class Triangle or Circle), and call the correct method for the derived class, instead of the base class.
This is done through abstract classes and virtual methods, as shown below (and discussed in depth in the lecture).
Example:
#include <iostream> using namespace std; // create the base class, with a pure virtual method, print // note that such methods are assigned a null pointer (the 0) // rather than being given an actual implementation class shape { public: virtual void print() = 0; }; // create a derived class, this time providing // an implementation for the virtual method class triangle: public shape { public: virtual void print() { cout << "I am a triangle" << endl; } }; // create another derived class, again providing // an implementation for the virtual method class circle: public shape { public: virtual void print() { cout << "I am a circle" << endl; } }; int main() { // create a pointer for an instance of the base class, // it is ok to make this point at instances of // any classes derived from the base class! shape *s; // let the user pick a specific kind of shape, // allocate one, and set s to point to it cout << "Pick a shape: C for circle or T for triangle" << endl; char c; cin >> c; if (c == 'C') s = new circle; else s = new triangle; // call the print method through the pointer: // at run time, since print for s is a pure virtual method, // the program will identify which kind of shape s actually // points at and will call either triangle::print or // circle::print, whichever is appropriate! s->print(); } |
In essence the abstract base class, Shape, exists only as a placeholder, indicating which methods are shared across all its descendants and which ones will be dynamically bound (those with "method() = 0;" implementations).