Several features of C++ improve on C. The "Artist" example uses two such features: object-orientation and dynamic collections (The dynamic collections are part of the Standard Templates Library, the STL).

. Some programming tasks are much easier to perform using classes and inheritance, the essential tools of object orientation. This is because the program situation (the problem domain) is naturally object-oriented. The program "Shapes" is an example of this.
. Dynamic collections (also called "containers") are storage spaces, just like arrays are storage spaces. Unlike arrays, dynamic collections can grow indefinitely as the program runs (as long as there is still some memory available). That's why they are called "dynamic". "Artist", uses a "vector", one type of dynamic collection, to store an indefinite number of shapes.

The program allows the creation of artistic screens composed of many coloured shapes drawn on top of each other.
- A variety of basic shapes are created and displayed one by one by pressing different keys. Once created each shape is stored into a sequential collection (a vector).
- All shapes in the collection can be drawn sequentially (on top of each other) at any time to give a colourful composition.
- At any time more shapes may be added, or the vector can be emptied and all shapes wiped off the screen.

As we build "Artist" and see how the program works, we also learn how to build object-oriented C++ projects composed of several classes. We discover the advantages of class inheritance. We learn how to use vectors and the STL, We also see how to use WinBGIm, a simple library of 2D graphics functions.

Shapes is a deceptively simple C++ program. It would be MUCH!! harder to implement in C without object-orientation. However to be able to design and write such "simple magic" ourselves, we have to understand key concepts of object orientation expressed in the weird and wonderful terms below.


Key Concepts: inheritance | base class | derived class | virtual method | abstract class | abtract method | virtual destructor | polymorphism | vector | STL


Before we start

Create a "Artist" subdirectory somewhere you choose to build this example.

Get the example from the zip file: image: files ArtistForWinBGIm.zip

Save the archive into the new "Artist" subdirectory, and extract the source files from the archive.
___________

Creating the "Shapes" project

With Code::Blocks, create a new WinBGIm project in the new "shapes" directory. Call the project Artist.
In the project "management" screen, right-click on the project name and select [Add files...]. Select and add all the ".cpp" and ".h" files to the project.

It shows 3 .cpp files shapes.cpp, Box.cpp and Ellipse.cpp, but there is more to this project than meets the eye:

There are 4 class header files in the project directory: Shape.h, Box.h, Ellipse.h, Circle.h. Each of these contains the class declaration for a class of the same name. The shapes project uses these 4 C++ classes.

- The header (.h) files are the public face of classes. They contain the class declaration. A class declaration states what data each object of that class contains, what functions (called methods for classes) can be called for each object. It gives the functions prototypes (the methods signatures).

- The .cpp files on the other hand define the class methods. They store the code for the functions, at least the most complex functions, as basic functions are allowed to be defined inline in the class header.


The Shapes project is composed of 4 classes:
"Shape" is the base class. Every object displayed on the program screen is a Shape. "Box" (a rectangle in fact), "Ellipse", and "Circle" (a special kind of circle) are all derived classes. They derive from Shape. We also say that they inherit from Shape.

We say that a derived class (also called a sub-class) inherits from a base class (also called a superclass).

The project uses derivation (public inheritance in C++ terms) between these classes to make use of the fact that boxes, ellipses and circles are shapes. The diagram on the right uses a triangle to indicate the inheritance relationship. (This conforms with the UML notation for class relationships.)

As we can see, Box is a Shape, Ellipse is a Shape, Circle is an Ellipse (and therefore also is a Shape).

The Shape class is drawn with dashes to indicate it is an abstract class (see further down).

project window
Shapes Project - Class Diagram

Inheritance represents the   is_a  relationship between classes


Notice that there are no .cpp files for classes Shape and Circle. This is because the code for these classes is so basic that it is completely defined inline within the Shape.h and Circle.h headers.
This is indicated by the use of the inline keyword at the start of functions declarations in the header.
___________

Building and running the "Artist" executable

You can open any of the project files that are part of the project by double-clicking on their name in the project window.

Before we do this however, let us compile and link "Artist", and play a little with the program to get familiar with it:

Select Build.

Code::Blocks compiles the 4 ".cpp" files with the headers that they #include, then links them together with the WinBGIm library to produce the executable "Artist.exe". The commands it uses to compile and then link are displayed into a "Build log" at the bottom of screen.


build image

You can now run the program by clicking on the executable filename in a file manager, by calling the program at a console, or within Code::Blocks by clicking on Build->Run.

The program gives some basic instructions on top of the screen:
The user can select 3 kinds of shapes by pressing the associated key, a Box (a rectangle with vertical and horizontal sides), an Ellipse, or a Circle. The shape dimensions and colors are determined by the random number generator. The selected shape is shown on the screen. A "burst" of shapes can be obtained by leaving the finger on the key for a short time.

The selected shapes are added (as Shape pointers) to a vector container. All the shapes in the collection can be shown at once by pressing the | [D]raw Shapes | option key (D).
___________

How does it work?

We will first examine the driver "Artist.cpp" (the code that makes all the classes work together).

"Artist" uses the standard C random number generator function rand() to obtain values for the geometric dimensions of each shape requested by the user.

When B, E, or C are pressed:
- An instance of the requested shape class (a box, or ellipse, or circle object) is created using new and passing the geometric data to the class constructor.
- The sceen is then cleared and the new shape is drawn with
   GrClearClipBox(BG_COLOR); // Clears the screen, except the menu bar
   theShape->draw(); // Show only the new shape
- A pointer to the created object (theShape) is placed inside the collection of shapes (a vector called shapes) with
   shapes.push_back(theShape);

The beauty of vectors is that they grow as needed. They can never fail when adding data, unlike arrays that can crash your program if it tries to store more into an array than the array can contain.
In this project there is no way of knowing in advance how many shapes the user will request, therefore a vector is all indicated. The vector of shapes is created as a local variable in the main() function with:
   vector<Shape *> shapes; // Collection of stored shapes
The parameter between the angle brackets < and > is called a "type parameter". It indicates what variable type (Shape pointers in this case) the shapes vector will store.

When D is pressed:
- The screen is cleared, then the shapes in the vector are drawn sequentially, one by one on top of each other in a for loop:
   for(int i = 0; i < numShapes; i++)
      shapes[i]->draw();

When W is pressed:
- The screen is wiped clean, then the memory previously allocated to all the shapes in the vector is released, each shape in its turn with:
   for(int i = 0; i < numShapes; i++)
      delete shapes[i];

delete is passed the Shape instance pointer previously created with new.

Notice that we obtain how many shapes have been stored in the vector shapes with the statement:
  int numShapes = shapes.size();
The vector at any time knows how many elements it contains... Easy!

___________

Into the Guts of Classes

The simplicity of this program is deceptive. It relies on the expressive power of inheritance and polymorphism available to C++ and to all object-oriented languages. Without object-orientation this code would be much more messy. Let's examine how shapes uses these features:

Inheritance
Boxes and ellipses are shapes. The Box and Ellipse classes therefore inherit from the Shape class. This is shown by the public inheritance in the class declarations:
class Ellipse: public Shape
{
. . .
class Box : public Shape
{

Private and protected inheritance types also exist in C++. They are used extremely rarely and do not represent the is_a relationship between classes. We will ignore them in this document.

A circle is a kind of ellipse (where the two axes are of equal dimensions). Therefore the Circle class inherits from the Ellipse class with:
class Circle : public Ellipse
{

What inheritance means in practice is that all the code and data that exist in the base class also exist in the derived class. It has been "inherited" by the derived class.
There is no need for example to repeat the Ellipse draw() method within the Circle code as the ellipse function of the same name, inherited by Circle will do the job just as well.

If a function of a base class needs to be replaced by another with the same name and signature in the derived class, because the behaviour of the inherited function does not fit the expected behaviour of the derived class, this can be done simply by re-declaring the function in the derived class header and writing the new function code in the derived class .cpp file. We say in this case that the derived class function overrides the base class function.

For this to work however we must declare the method in the base class header to be virtual. Briefly, this in an instruction to the compiler to place the function address into a table kept by the program called the "virtual table" or V-table. The virtual table allows the program at runtime to find the proper function, even though there may exist several functions with the same name and signatures in the class hierarchy.

Note that the default destructor is a special class function that must be declared virtual in the base class. This allows the proper destructor to be called when a derived class object falls out of scope or is deleted. This is done in Shape with:
/**
 * Default destructor must be virtual to support inheritance
 */

virtual ~Shape() {}

Shape is a special kind of class called an abstract class.
No object of the abstract class Shape can be created as such. Shape instances can be created, but they will be created as Circle, Ellipse, or Box instances. This is because Shape has one method, draw(), that is declared in the header, but never defined. draw() is itself called an abstract method:
/**
 * Draw is abstract. It must be implemented by derived classes.
 */
virtual void draw() const = 0;

This is achieved by declaring draw() to be virtual and = 0.

Why bother with inheritance?
Inheritance saves coding. We can derive a new class from an existing base class. By doing so we inherit all previously working code from the base class. This saves work, is more efficient as the executable will be smaller, and is safer since we re-use existing (and hopefully tested and solid) code.

For example we could add a Square class and make it inherit from Rectangle... no need to re-write draw() then, as the mehod inherited from Rectangle will work just fine for Square:
class Square : public Rectangle
{

But these advantages pale in comparison with the magic of polymorphism that inheritance enables.

Polymorphism

The same function may have a variety of behaviours depending on the category of the object (the class) that executes it.

The "speak" function of Bird will probably chirp, but that of Dog is more likely to bark, etc...

In object-oriented jargon this is referred to as polymorphism.

The image on the right illustrates the polymorphism (the various forms) of speak.

Polymorphism of "Speak"


For the object-oriented programmer, polymorphism is just short of magic:

In shapes, the polymorphic behaviour of each object stored in the vector is invoked with the for loop:
   for(int i = 0; i < numShapes; i++)
       shapes[i]->draw();

The -> is required because each element in the vector is a pointer to a shape instance.



WinBGIm

The simple 2D graphics library WinBGIm is used here to actually draw the shapes on the screen. The code is very simple.
As an example, the polymorphic (virtual) method draw() is overriden in Ellipse with:

 void Ellipse::draw()
 {
    // if filled, draw with fillellipse
    if(filled)
    {
        setfillstyle(SOLID_FILL, color);
        fillellipse(centerX, centerY, width, height);
    }
    else // if not, draw with ellipse function
    {
        setcolor(color);
        ellipse(centerX, centerY, 0, 360, width, height);
    }
 }

Functions setfillstyle(), filledellipse(), setcolor(), and ellipse() are part of the WinBGIm function library. Their parameters are set from the various attributes of the object that must be drawn.
Basic information about these and other functions in the WinBGIm library is given on the help menu: Help->Programer's Help->WinBGIm Library in the Code::Blocks EDU-Portable IDE.


- WinBGIm is a 2D graphics drawing package for C or C++. Using WinBGIm does not require event-driven programming; it is therefore very simple to use by novice programmers. The library supports displaying shapes and images, loading fonts and displaying text, catching mouse clicks and mouse position and catching key-presses.
Despite its simplicity, WinBGIm offers all that is necessary for simple computer games and simulation programs. It is an emulation of the Borland Graphical Interface (BGI) that was once used to write many games.
___________
- You'll understand really well how this code works by implementing your own modifications and extensions to this example.