Parametric Polymorphism

C++ uses the key word template to provide parametric.  Parametric polymorphism allows the same code to be used with respect to different types.  Parametric polymorphism is especially useful for capturing algorithms.  With the use of parametric polymorphism we can rewrite the averageArr example from lecture 2 in the following manner:

template <class T> T averageArr(const T arr[], int len)
{
    double sum=0;
    for (int i = 0; i<len; i++)
        sum = sum + arr[i];

    return ((T)sum / len);
}

The general form of a template function is the reserve word template followed by list of formal type parameters inside the <>, followed by return type,  identify and parameter list in parentheses. Note it is general practice not to prototype a template function. Templates  sould be used when a function's algorithm does not change with different types. Overloading should be used when the function algorithm does vary with different types. Note that templates and overloading are not mutually exclusive. For example:

template <class T> void display(const T arr[], int size)
{
    cout << arr[0];
    for (int i =0; i<len; i++)
        cout << ", " << arr[i];
    cout << endl;
}

template <class T> void display(const T value)
{
    cout << value << endl;
}

Note you can template multiple parameters in the following manner:

template <class T,  class S> void display(const T value, const S label)
{
    cout << lablel << " " << value << endl;
}
 

Dynamic Memory

This is one of the most powerful features of C++.  Dynamic Memory allows the developer to write a program which expand and modify's it behavor based on external events.  With dynamic memory a developer nolonger need to guess at the maxuim size of arrays. The general form  is as follows

int  j=100;
int *jPtr;

// Point jPtr to j
jPtr = &j;    // jPtr now contains j's address location

// To assign 200 to j
// Derference jPtr and set it equal to 200
*jPtr = 200;

Example Program:

#include <iostream>
using namespace std;

int
main()
{
    int y=100;
    int x=-20;

    // Point yPtr to y
    int *yPtr = &y;

    cout << "Before modification of *yPtr." << endl;

    cout << "&yPtr: " << &yPtr << " yPtr: " << yPtr << endl;
    cout << "&y: " << &y << " y: " << y;

    // Dereference
    cout << " *yPtr: " << *yPtr << endl;

    // Dereference yPtr and set it equal to 2112
    *yPtr = 2112;
    cout << endl << "After modification of *yPtr." << endl;

    cout << "&yPtr: " << &yPtr << " yPtr: " << yPtr << endl;
    cout << "&y: " << &y << " y: " << y;

    // Dereference yPtr
    cout << " *yPtr: " << *yPtr << endl;

    // Point yPtr to x
    yPtr = &x;
    cout << endl << "After modification of yPtr." << endl;

    cout << "&yPtr: " << &yPtr << " yPtr: " << yPtr << endl;
    cout << "&x: " << &x << " x: " << x;

    // Dereference yPtr
    cout << " *yPtr: " << *yPtr << endl;
 

    return 0;
}

Output from program:

Before modification of *yPtr.
&yPtr: 0012FF7C yPtr: 0012FF74
&y: 0012FF74 y: 100 *yPtr: 100

After modification of *yPtr.
&yPtr: 0012FF7C yPtr: 0012FF74
&y: 0012FF74 y: 2112 *yPtr: 2112

After modification of yPtr.
&yPtr: 0012FF7C yPtr: 0012FF78
&x: 0012FF78 x: -20 *yPtr: -20
Press any key to continue

Import compiler error message note must compiliers will from time to time spit out the following message:
error C2166: l-value specifies const object or error <number>: l-value ....   lvalue stands for left hand value in other words a value that can be assign to.
Dereferenced pointers are lvalues. The code which generated this error is as follows:

    const int i=0;
    i = 100;

Allocating Memory off the Free Store (Heap)

Up to this point in the class all of the memory we have directly used has been gotton off the stack.  C++/C allows us to allocate memory at run time by using Free Store (Heap).  There are two commands that allow use to allocate memory from the free store malloc and delete.  malloc is the old C style of allocating memory.

For example:
    // C Style
    int *aPtr = (int*)malloc(sizeof(char));  // Note we must cast the void pointer returned by malloc to the approperate type.

     // C++ Style
     int *aPtr = new int();

Once a developer is through using memory they must return to the free store, unless they want a memory leak in their code. Memory gotten with malloc must be freed with free and memory gotten with new deleted by delete.

For example:
    free(aPtr);
   delete aPtr;

With this new mechism a developer can now allocate arrays dynamiclly.  The following code will allocate an array of n charactors.

#include <iostream>
using namespace std;

char*  createCharArr(const int size)
{
   char *array = new char[size];
   memset(array, 0x00, size);
   return array;
}

void  main()
{
    char *arr = createCharArr(6);
    // Note c string "hello" is six charators long
    // the compiler automatically puts an null terminator on it
    strcpy(arr, "hello");

    cout << "Set iterator to the begining." << endl;

    // Print out each individual char
    char *iterator = &arr[0];
    while (*iterator != '\0')
        cout << *iterator++ << endl;

    cout << "Set iterator back to the begining." << endl;

    // Set iterator back to begining
    iterator = &arr[0];
    do
    {
      cout << *iterator << endl;
    } while (*++iterator != '\0');

    delete [] arr;
}
 

Two expand on the pointer arithmatic above  is an example of using pointers to display abitary arrays.

#include <iostream>
using namespace std;

template <class T> void Display(const T* begin, const T* end)
{
    T* iter = (T*)begin;

    while (iter != (end + 1))
        cout << *iter++ << endl;
}
 

int main()
{
    int intArr[5] = { 1, 2, 3, 4, 5};
    int* iterator = &intArr[0];

    for (int i = 0; i < 5; i++)
        cout << *iterator++ << endl;

    cout << endl << "Using generic template function to display int array data." << endl;
    Display(&intArr[0], &intArr[4]);

    cout << endl << "Using generic template function to display C string array data." << endl;
    char *strs[3] = { "One", "Two", "Three" };

    Display(&strs[0], &strs[2]);

    cout << endl << "Using generic template function to display double array data." << endl;
    double doubleArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1000.23 };

    Display(&doubleArr[0], &doubleArr[9]);
 

    return 0;
}
 

Function Pointers

C++/C allows the developer to get the address of function.  This allows the programmer to write very dynamic programs. As we will see in later lectures this ablity is at the heart of Dynamic binding or pure polymorphism.   One the common uses is the callback technic employeed by many GUI interfaces.  The following code represents a callback.

#include <iostream>
using namespace std;

enum EVENT_TYPE { BUTTON_PRESSED, BUTTON_ARMED };

struct GUIEvent
{
 
    EVENT_TYPE Event;
    char       Msg[80];
};
 

#define CALLBACK bool(*)(const GUIEvent*, void *)
#define CALLBACKPTR(NAME1) bool(*NAME1)(const GUIEvent*, void *)
#define CALLBACK_PROTO(NAME) bool NAME(const GUIEvent* event, void* user_data)

CALLBACKPTR(gButtonPressedCallback);
void* gButtonPressedUserData;

CALLBACKPTR(gButtonArmedCallback);
void* gButtonArmedUserData;
 

CALLBACK_PROTO(HandleButtonPressed);
CALLBACK_PROTO(HandleButtonArmed);
void AttachCallback(EVENT_TYPE type, CALLBACK, void*);

void DispatchLoop()
{
    bool NotDoneProcessing=true;
    char buf[80];
    char ch;
    GUIEvent event;

    while (NotDoneProcessing)
    {
        cout << "Enter 'P' for Button pressed. " << endl;
        cout << "and 'A' for Button armed. (X for quit) " << endl;
        cin >> ch;
        cin.getline(buf, 80);

        cout.flush();

        switch(ch)
        {
        case  'P' : event.Event = BUTTON_PRESSED;
                    strcpy(event.Msg, "Button Pressed");
                    gButtonPressedCallback(&event, gButtonPressedUserData);
                    break;
        case  'A' : event.Event = BUTTON_ARMED;
                    strcpy(event.Msg, "Button Armed");
                    gButtonPressedCallback(&event, gButtonPressedUserData);
                    break;
        case  'X' : NotDoneProcessing = false;
                    break;
        default :
            cerr << "Illegal Value." << endl;
        }
        cout << endl;
    }
}

int main()
{
    char user_data[10] = "hello";
    AttachCallback(BUTTON_PRESSED, &HandleButtonPressed, (void*)user_data);
    AttachCallback(BUTTON_ARMED, &HandleButtonArmed, (void*)user_data);

    DispatchLoop();
    return 0;
}
 
 

CALLBACK_PROTO(HandleButtonPressed)
{
    cout << "In HandleButtonPressed: " << endl;
    cout <<    (char *)user_data << endl;
    cout <<     event->Msg << endl;
    return true;
}

CALLBACK_PROTO(HandleButtonArmed)
{
    cout << "In HandleButtonArmed: " << endl;
    cout <<    (char *)user_data << endl;
    cout <<     event->Msg << endl;
    return true;
}
 

void AttachCallback(EVENT_TYPE type, CALLBACKPTR(funcPtr), void* user_data)
{
    switch (type)
    {
    case BUTTON_PRESSED :
        gButtonPressedCallback = funcPtr;
        gButtonPressedUserData = user_data;
        break;
    case BUTTON_ARMED :
        gButtonArmedCallback = funcPtr;
        gButtonArmedUserData = user_data;
        break;
    }
}
 

C Strings