Contents  1-5  6-11  12-16  17-21  22-27   28-33  34 - 38  39-46  Projects   MFC

   Contact
   Search
   C
   C++
   Visual Basic
   Java
   JavaScript
   DHTML
   Style Sheets
   About
   Active X
   TDC Binding
   PHP
   Perl and CGI
   Flash
   XML
   SQL
   Messages
   Chat
   MCSE
   Linux
   Cabling   
   ActionScript
   Downloads
   E-Cards   
 
    
    

Advanced Member Functions incorporate overloading, that is writing two or more functions with the same name but
with different parameters/arguments and return types.  This is function polymorphism.  Let's rephrase this as an
important new keyword:

"All parts should go together without forcing.  You must remember that the parts you are reassembling were disassembled by you.  Therefore, if you can't get them together again, there must be a reason.  By all means, do not use a hammer." - 1925 IBM Maintenance Manual

Overloaded Member Functions - Overloaded member functions are part of function polymorphism, writing two or more functions with the same name but different parameters (arguments) and return types.  Example:

#include <iostream.h>

typedef unsigned short int USHORT;

enum BOOL { FALSE, TRUE};

//----------------------------------------------------------------------------------------------------------------

class Rectangle
{
      public:
      Rectangle(USHORT width, USHORT height);
      ~Rectangle(){}

     
//overloaded class function DrawAShape
      void DrawAShape() const;
      void DrawAShape(USHORT aWidth, USHORT aHeight) const;

      private:
      USHORT itsWidth;
      USHORT itsHeight;
};


//Constructor implementation outside class
Rectangle::Rectangle(USHORT width, USHORT height)
{
     itsWidth = width;
     itsHeight = height;
}


//Overloaded DrawAShape - takes no values. Draws based on current class member values
void Rectangle::DrawAShape() const
{
     DrawAShape( itsWidth, itsHeight);
}


//Overloaded DrawAShape - takes two values and draws shape based on the parameters
void Rectangle::DrawAShape(USHORT width, USHORT height) const
{
     for(USHORT i = 0; i<height; i++)
     {
         for(USHORT j = 0; j< width; j++)
         {   cout << "*";  }
    
     cout << "\n";
     }
//end first for loop
}
// close function

//----------------------------------------------------------------------------------------------------------------

//main() is the driver program to demonstrate overloaded functions
int main()
{
    Rectangle ARectangle(30,5);

    cout << "DrawAShape(): \n";

    ARectangle.DrawAShape();    
  //calls overloaded function with no parameters

    cout << "\nDrawAShape(40,2): \n";

    ARectangle.DrawAShape(40,2); 
 //calls overloaded function that takes 2 parameters

    return
0;
}
Output: DrawAShape(): 
******************************
******************************
******************************
******************************
******************************
DrawAShape(40,2):
****************************************
****************************************

The function, DrawAShape(), is overloaded.  The main() function creates a Rectangle object and calls its overloaded DrawAShape() method, passing in 1st no parameters.  In this way the compiler knows to call the first overloaded function that takes no parameters.   It then calls DrawAShape() again, passing in 2 unsigned short integers.  In this way the compiler knows to call the second overloaded function that takes two parameters.  The same function name can be used, and this hides the interface somewhat.  The overloaded functions will "do the right thing" based upon how they are called.  When overloading a function, for each different data type and number of parameters, another function of the same name but corresponding return type and arguments is required.  The DrawAShape() that takes no parameters calls the version of DrawAShape() that takes two parameters, passing in the current member variables. 

Using default values - member functions of a class can have default values (same as non-class functions).

//Default values in functions
#include <iostream.h>

typedef unsigned short int USHORT;

enum BOOL { FALSE, TRUE};

//----------------------------------------------------------------------------------------------------------------

class Rectangle
{
      public:
      Rectangle(USHORT width, USHORT height);
      ~Rectangle(){}

      void DrawAShape(USHORT aWidth, USHORT aHeight, BOOL UseCurrentVals = FALSE) const;

      private:
      USHORT itsWidth;
      USHORT itsHeight;
};


//Constructor implementation outside class, values assigned shorthand
Rectangle::Rectangle(USHORT width, USHORT height):
itsWidth(width), itsHeight(height) {}


//Default values used for third parameter
void Rectangle::DrawAShape(USHORT width, USHORT height, BOOL UseCurrentValue) const
{
     int printWidth;
     int printHeight;

     if(UseCurrentValue == TRUE)
     {
         printWidth = itsWidth;
     //use current class values assigned when object instantiated
         printHeight = itsHeight;
     }
     else
     {
         printWidth = width;      
//use parameter values passed in as arguments
         printHeight = height;
     }


     for(int i = 0; i<printHeight; i++)
     {
          for(int j = 0; j< printWidth; j++)
          {
              cout << "*";
          }
       cout << "\n";
     }
}

//----------------------------------------------------------------------------------------------------------------

int main()
{
     //Initialize a rectangle to 10,20, call the constructor
   Rectangle ARectangle(30,5);

   cout << "DrawAShape(0,0,TRUE)...\n";

   ARectangle.DrawAShape(0,0,TRUE);   
//call function passing in all 3 parameters

   cout <<"DrawAShape(40,2)...\n";

   ARectangle.DrawAShape(40,2);      
//call function passing in 2 parameters, use default parameter for 3rd

   return
0;
}
Output: DrawAShape(0,0 TRUE): 
******************************
******************************
******************************
******************************
******************************
DrawAShape(40,2):
****************************************
****************************************

Using a default value replaces the overloaded DrawAShape() function in the previous example with a single function with default parameters.  The function is declared to take 3 parameters.  The first 2 are USHORTS, the 3rd, "UseCurrentValues", is a BOOL that defaults to false.  The implementation of function is outside of the class.  The 3rd parameter, "UseCurrentValue", is evaluated.  If it is true, the Rectangle class member variables "itsheight" and "itswidth" are used to set the local function variables "printWidth" and "printHeight".  If "UseCurrentValue" is false, either by default or as set by the user, the first two parameters are used for setting printWidth and printHeight rather than the class's data members.

Boolean values - values that evaluate to true or false.  C++ considers 0 to be false and all other values true.

Use function overloading when:
1) There is no reasonable default value.
2) You need different algorithms.
3) You need to support various types in your parameter list.

Default constructor - If you do not explicitly declare a constructor for your class, a default constructor is created for you automatically.  Any constructor that takes no parameters is considered the "default" constructor.  If you create any constructors at all, the default constructor will not be created by the compiler and you will have to create it yourself.  The one that you create that takes no parameters will be considered default.

Overloading constructors - Constructors create objects (the Monster constructor creates a Monster).  Before the constructor runs, there is no Monster object, just an area of memory and a blueprint for a Monster object.  After the constructor is called, there is a complete Monster object. You might have a Monster object with 2 constructors.  The first takes a Strength and a Life parameter.  The second constructor may take no values and will therefore make a Monster of default size.  The compiler chooses the right constructor just as it does any overloaded function - based on the number and type of parameters.  Though you can overload constructors, NEVER overload destructors.  Destructors always have a ~ followed by the name of the class and no parameters.  Remember: DO NOT overload destructors.

When initializing objects in constructors, remember that constructors are created in 2 stages - the initialization and the body.  It is cleaner and more efficient to initialize variables at the initialization stage.  Example:

CAT():           //constructor name and parameters
KittyAge(5),    
//initialization list
KittyWeight(8)
{ }
//body of constructor

Place a colon after the closing parentheses on the constructor's parameter list .  Then put the name of the member variable and a pair of parentheses.  Inside, place the expression to be used to initialize that member variable.  If there is more than one expression, separate them with a comma.  This makes sort of a shorthand way of initializing objects with default values.

Remember that references and constants MUST be initialized and can not be reassigned to.  It is more efficient to initialize member variables than to assign to them.  This becomes evident with the copy constructor.

Copy constructor - The compiler also provides a default copy constructor.  When you pass an object by value, a temporary copy of the object is made.  If the object is user-defined, the class's copy constructor is called.  All copy constructors take one parameter - a reference to an object of the same class.  Make it const so the constructor will not alter the object passed in.  The default copy constructor copies the object passed in as a parameter to the member variables of the new object.  This is a member-wise or "shallow" copy.  Example:

CAT (const CAT &theCat);

Shallow Copy - A shallow copy copies the exact values of one object's member variables into another object. 
                      Pointers in both objects end up pointing to the same memory. (also called a "member-wise" copy).

Deep copy - A deep copy copies the values allocated on the heap to newly allocated memory. 

Problem:  Shallow copy.  The CAT class includes a member variable, KittyAge, that points to an integer created on the free store (heap).  The default copy constructor will copy the KittyAge member variable passed in from CAT to the new CAT's KittyAge member variable.  The two objects will then point to the same memory.  This will lead to disaster if either CAT goes out of scope.  When the object goes out of scope, the destructor is called and will attempt to clean up the allocated memory.  A copy will be left still pointing to that memory, and if it tries to access the memory, it will crash the program. 

Solution:  Deep copy.  Define your own copy constructor and allocate the memory as required.  Once the memory is allocated, the old values can be copied into the new memory.  This is called a "deep copy".  Example:

//Defining your own copy constructor for a DEEP copy
#include <iostream.h>

//----------------------------------------------------------------------------------------------------------------

class CAT
{
      public:
      CAT();
      CAT (const CAT &);   
//copy constructor
      ~CAT();               
//destructor

     
//Accessor methods
      int GetAge() const { return *KittyAge; }
      int GetWeight() const { return *KittyWeight; }
      void SetAge(int age) { *KittyAge = age; }

      private:
      int *KittyAge;
      int *KittyWeight;
};

//Constructor supplies default values, creates int objects on heap
CAT::CAT()
{
    KittyAge = new int;      
//have address point to new int on heap
    KittyWeight = new int;
    *KittyAge = 2;           
//dereference to assign a value
    *KittyWeight = 10;
}


//Define our own copy constructor for a deep copy.  "rhs" = right hand side.
//Copies all data members from one CAT object to another and creates new variables on the heap

CAT::CAT(const CAT & rhs)
{
    KittyAge = new int;
    KittyWeight = new int;
    *KittyAge = rhs.GetAge();
    *KittyWeight = rhs.GetWeight();
}


//Need to add code to our destructor to clean up heap objects crated with constructor
CAT::~CAT()
{
    delete KittyAge;
    KittyAge = 0;       
//for safety, initialize pointer to null after delete

    delete KittyWeight;
    KittyWeight = 0;    
//for safety, initialize pointer to null after delete
}

//----------------------------------------------------------------------------------------------------------------

int main()
{
    CAT Tiger;

    cout << "Tiger's age: " << Tiger.GetAge() << endl;
    cout << "Setting Tiger to 9...\n";

    Tiger.SetAge(9);

    cout << "Creating Casey from Tiger\n";
   
    CAT Casey(Tiger);

    cout << "Tiger's age: " << Tiger.GetAge() << endl;
    cout << "Casey's age: " << Casey.GetAge() << endl;
    cout << "Setting Tiger to 3...\n";

    Tiger.SetAge(3);

    cout << "Tiger's age: " << Tiger.GetAge() << endl;
    cout << "Casey's age: " << Casey.GetAge() << endl;

    return 0;
}
Output: 
Tiger's age: 2
Setting Tiger to 9...
Creating Casey from Tiger
Tiger's age: 9
Casey's age: 9
Setting Tiger to 3...
Tiger's age: 3
Casey's age: 9

The CAT class declared and a default constructor is declared.  Then the copy constructor declared.  In the copy constructor, two member variables are declared as pointers to an integer.  Typically there is little reason for a class to store integer(int) member variables as pointers, but this was done to illustrate how to manage member variables on the free store.

The default constructor allocates room on the free store for two int variables and then assigns values to them.Notice in the copy constructor there is a parameter is called "rhs".  It is common to refer to the parameter of a copy constructor as "rhs", which stands for "right hand side".  This signifies that is is a reference/alias to what is on the right side of the = (assignment operator).  It copies what is passed in on the right to what is on the left.  In this case a reference to a CAT object is passed in and copied as a const reference to a CAT.

The assignments here have the object passed in as a parameter on the right side of the = sign.  Memory is allocated on the free store, then the value at the new memory location is assigned the values from the existing CAT.  The parameter "rhs" is a CAT that is passed into the copy constructor as a constant reference.  The member function "rhs.GetAge()" returns the value stored in the memory pointed to by rhs's member variable, KittyAge.  In this way the CAT object rhs has all the member variables of any other CAT.  When the copy constructor is called to create a new CAT, an existing CAT is passed in as a parameter.  The new CAT can refer to its own member variables directly, but it must access rhs's member variables using the public accessor methods.  The values pointed to by the existing CAT are copied to the memory allocated for the new CAT on the heap.  Rather than pointers pointing to the same memory location, new, separate locations on the heap are set up to store value for each object, though their values get copied.

After Tiger is instantiated,  a new CAT is created,  Casey,  using the copy constructor and passing in Tiger as the object to be copied. (Had Frisky been passed as a parameter to a function, this same call to the copy constructor would have been made by the compiler.)  We have hence taught it how to copy data members from one object to another via passing in the object name.  It's a bit of shorthand.

When tiger's age is 3, but Casey's age is still 9, it demonstrates that they are stored in separate areas of memory on the heap.  When the CAT objects fall out of scope, their destructors are automatically invoked.  Implementation of the CAT destructor includes the "delete" keyword.  It is called on both the pointers "KittyAge" and "KittyWeight", returning the allocated memory to the free store.  For safety, the pointers are reassigned to NULL.


©2004 C. Germany