Abstract Classes and Polymorphism Revisited

I’m on a fundamentals trip at the moment, so I’m going back over things I mostly understand to ensure I really understand the principles involved and not just in a vague hand-waving kind of way. Today’s fundamental revisited is abstract (pure virtual) classes and polymorphism in C++ – it’s commented to the hilt to make clear not only what’s going on but WHY it’s going on. You wouldn’t generally comment your code this heavily, but as a reminder/guide I’m fine with it:

#include <iostream>
 
using namespace std;
 
/*** ----- Shape base class - (Abstract)----- ***/
 
class Shape
{
public:
        // Only a single method needs to be a pure virtual function (which we use the '= 0'
        // phrasing to denote) to make the class an Abstract class. But we'll assign them all
        // to be pure virtual functions with no implementation.
        //
        // Note: Any class which inherits from an abstract class must implement ALL methods
        // of that abstract class!
        virtual string type()      = 0;
        virtual float  area()      = 0;
        virtual float  perimeter() = 0;
 
        // Because Shape is an abstract class we should provide it with a virtual destructor
        // If we didn't declare this as virtual then it wouldn't get called when the destructor
        // of an object of a derived class is called.
        virtual ~Shape()
        {
            cout << "Virtual Shape destructor called!" << endl;
        }
};
 
 
/*** ----- Circle derived class ----- ***/
 
class Circle : public Shape
{
public:
        // Constructor declaration
        Circle(float rad);
 
        // Derived destructor
        ~Circle()
        {
            // Nothing to do here as radius property is not allocated on the heap using the 'new' keyword.
            // If it was, however, then this would be where we'd have to free that memory.
            cout << "Derived Circle destructor called!" << endl;
 
            // Note: After this, as a Circle is a type of Shape, the Shape virtual destructor will be called.
        }
 
        // Polyomrphic public methods
        string type();
        float  area();
        float  perimeter();
 
protected:
        float radius;
};
 
Circle::Circle(float rad) : radius(rad)
{
    /***
     * Member property set in initialisation list for efficiency.
     * If we set the property in the constructor then we perform TWO operations:
     *
     *     - We construct the member property with a default value,
     *     - We then overwrite that default value with our constructor parameter.
     *
     * If we use the parameter list to initialise the value, we only perform ONE operation,
     * which is to initialise the property with the parameter we passed in. This can have
     * significant performance implications when working with large arrays of objects.
     ***/
}
 
string Circle::type()     { return "Circle";                }
float Circle::area()      { return 3.14f * radius * radius; }
float Circle::perimeter() { return 2.0f * 3.14f * radius;   }
 
 
/*** ----- Rectangle derived classs ----- ***/
 
class Rectangle : public Shape
{
public:
        // Consturctor declaration
        Rectangle(float theWidth, float theHeight);
 
        // Derived destructor
        ~Rectangle()
        {
            // Nothing to do here as width and length properties are not allocated on the heap using the 'new' keyword.
            // If they were was, however, then this would be where we'd have to free that memory.
            cout << "Derived Rectangle destructor called!" << endl;
 
            // Note: After this, as a Rectangle is a type of Shape, the Shape virtual destructor will be called.
        }
 
        // Polymorphic public methods
        string type();
        float  area();
        float  perimeter();
 
protected:
        float width;
        float length;
};
 
Rectangle::Rectangle(float theWidth, float theLength) : width(theWidth), length(theLength)
{
    // Constructor uses initialisation list rather than initialising members in constuctor.
    // See Circle constructor for reasons why.
}
 
string Rectangle::type()     { return "Rectangle";             }
float Rectangle::area()      { return width * length;          }
float Rectangle::perimeter() { return 2.0f * (width + length); }
 
 
int main()
{
    // Create an array of 10 pointers to Shape objects
    // Note: The shape objects are not instantiated to any specific class as yet!
    // In fact, as Shape is now an abstract class we CANNOT instantiate an object of type Shape!
    // That is, the following would cause a compile-time error: Shape myShape;
    Shape *shapes[10];
 
    // Create 5 Circles
    shapes[0] = new Circle(1.0f);
    shapes[1] = new Circle(2.0f);
    shapes[2] = new Circle(3.0f);
    shapes[3] = new Circle(4.0f);
    shapes[4] = new Circle(5.0f);
 
    // Create 5 Rectangles
    shapes[5] = new Rectangle(1.0f,  2.0f);
    shapes[6] = new Rectangle(2.0f,  4.0f);
    shapes[7] = new Rectangle(4.0f,  8.0f);
    shapes[8] = new Rectangle(8.0f,  16.0f);
    shapes[9] = new Rectangle(16.0f, 32.0f);
 
    // Print the shape object details for each object
    for (int loop = 0; loop < 10; loop++)
    {
        string type      = shapes[loop]->type();
        float  area      = shapes[loop]->area();
        float  perimeter = shapes[loop]->perimeter();
 
        cout << "Shape[" << loop << "]'s type = " << type << ", area = " << area << ", perimeter = " << perimeter << endl;
    }
 
    // GOTCHA: We should NOT try to free the allocated pointer to the shapes array using:
    //      delete[] shapes;
    //
    // The reason being that the array of pointers to shapes was declared like this: Shape *shapes[10];
    // This means that the array of pointers to shapes is on the STACK and not the HEAP!
    // The objects themselves are on the heap, but the pointers to objects are on the stack and
    // as such don't need to (and shouldn't) be freed!
    //
    // Instead, we should free the memory used by each shape like this:
    // Note: As we have an array of pointers to Shapes, and Shape has a virtual destructor,
    // the derived class destructor will be called and then the base class destructor will be called.
    for (int loop = 0; loop < 10; loop++)
    {
        delete shapes[loop];
    }
 
    return 0;
}

What would happen if we did NOT declare Shape as an abstract class? Well, we’d have to provide implementations for the type(), area() and perimeter() methods for Shape – but that’s about it. The polymorphism part would still work, that is, we could still create an array of pointers to a Shape, and then instantiate each shape as a Circle or a Rectangle as we see fit – and the correct functions would execute for each derived class.

However, it makes good sense to declare Shape as an abstract class so that we can’t instantiate it (even if we wanted to), in the same way that we’d make a Car class abstract. You can’t go out and buy a Car – it’s generic. You can buy a Ford Escort, or a Hyundai i30 or even a Ferrari F40, but you can’t buy a car in the same way that you can’t (technically) eat food or drink beer.

Doesn’t stop me trying though. Cheers! =D

An introduction to ActionScript 3.0 – week 4 lesson 2

Learning actionscript? Want to know the gist of object creation and extending classes to do neat things? Then you’ve come to the right place…

Flash Week 4 Lesson 2

Download slides link: An Introduction to ActionScript 3.0 – Week 4, Lesson 2
Audience: Beginners who know a little about variables and how to perform some basic programming math.
Format: Powerpoint 2003 (so they can be opened in LibreOffice/OpenOffice, MS Office 2K3/2K7/2K10 etc.)
Template: OOo2 by Chih-Hao Tsai
Content License: These slides are released under a creative commons non-commercial attribution share-alike 3.0 license by me (r3dux) and come with no guarantee of correctness, fitness for purpose or anything of the sort.

EnJoY!