|

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
|