What is the point of polymorphism if I can't group derived objects together under their base class? What's the point of inheritance if the base class must know everything about its derived members?
C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object that receive the method call. Indeed it involves inheritance.
Typical example would be the Shape class which would have 3 subclasses: Circle, Rectangle and Triangle. A (closed) shape defines an area, so you could have a virtual method area()
defined at the shape class level and implemented differently in each subclass. That’s polymorphism. And because you have a true purpose for inheritance, it’s not that you need to know everything but just conform to the fact that a shape needs to know how to calculate its area.
From a compiler perspective, defining a virtual function in a base class , with other implementation in derived classes, tells the compiler that you don't expect static linkage for this function but dynamic linkage (aka late binding): we want the selection of the function to be called at any given point in the program to be based on the kind of object for which it is called.
for example if you run this code (without the area() method being virtual):
class Shape {
protected:
int xPos, yPos;
int boundingBoxWidth, boundingBoxheight;
public:
Shape(int x = 0, int y = 0, int w = 0, int h = 0): xPos(x), yPos(y), boundingBoxWidth(w), boundingBoxheight(h) {}
float area() {
Serial.print(F("@Shape: "));
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle(int x = 0, int y = 0, int w = 0, int h = 0): Shape(x, y, w, h) { }
float area () {
Serial.print(F("@Rectangle: "));
return (boundingBoxWidth * boundingBoxheight);
}
};
class Triangle: public Shape {
public:
Triangle(int x = 0, int y = 0, int w = 0, int h = 0): Shape(x, y, w, h) { }
float area () {
Serial.print(F("@Triangle: "));
return (boundingBoxWidth * boundingBoxheight / 2.0);
}
};
Rectangle aRectangle(10, 10, 10, 20);
Triangle aTriangle(0, 10, 20, 5);
Shape* shapes[] = {&aRectangle, &aTriangle};
uint8_t shapesCount = sizeof(shapes) / sizeof(shapes[0]);
void setup()
{
Serial.begin(115200);
for (byte i = 0; i < shapesCount; i++)
Serial.println(shapes[i]->area());
}
void loop() {}
it won't work as expected because of early binding to the array type Shape and you'll see in the console
[color=purple]@Shape: 0.00
@Shape: 0.00
[/color]
Now if you take the exact same code and add virtual
in front of the area() method in the base class, then the compiler sees the functions implemented in the subclasses and will use late binding. so this code
class Shape {
protected:
int xPos, yPos;
int boundingBoxWidth, boundingBoxheight;
public:
Shape(int x = 0, int y = 0, int w = 0, int h = 0): xPos(x), yPos(y), boundingBoxWidth(w), boundingBoxheight(h) {}
virtual float area() {
Serial.print(F("@Shape: "));
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle(int x = 0, int y = 0, int w = 0, int h = 0): Shape(x, y, w, h) { }
float area () {
Serial.print(F("@Rectangle: "));
return (boundingBoxWidth * boundingBoxheight);
}
};
class Triangle: public Shape {
public:
Triangle(int x = 0, int y = 0, int w = 0, int h = 0): Shape(x, y, w, h) { }
float area () {
Serial.print(F("@Triangle: "));
return (boundingBoxWidth * boundingBoxheight / 2.0);
}
};
Rectangle aRectangle(10, 10, 10, 20);
Triangle aTriangle(0, 10, 20, 5);
Shape* shapes[] = {&aRectangle, &aTriangle};
uint8_t shapesCount = sizeof(shapes) / sizeof(shapes[0]);
void setup()
{
Serial.begin(115200);
for (byte i = 0; i < shapesCount; i++)
Serial.println(shapes[i]->area());
}
void loop() {}
will generate in the console:
[color=purple]@Rectangle: 200.00
@Triangle: 50.00
[/color]
which is what we expected.