Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 70

Chapter XXVII

Focus on Object Oriented Programming


Constructors and Destructors

Chapter XXVII Topics


27.1 Introduction

27.2 Scope of an Object

27.3 Default Constructors

27.4 Overloaded Constructors

27.5 Constructor Initialization List

27.6 Destructors

27.7 Copy Constructors

27.8 Objects and Parameters

27.1 Introduction
Chapter XXVII Focus on OOP, Constructors and Destructors 27.1
This chapter will take a detailed look at constructors and destructors. The first
introduction to OOP, in chapter 17, introduced these special functions. Since our
return to OOP, constructors and destructors have been mentioned and used
without much attention to any particular details. The time has come to pay very
close attention to these unique functions.

The last chapter focused on member functions. In a sense, this chapter continues
that trend by looking at two specialized member functions. Constructors and
destructors are functions that are part of a class. This makes them member
functions. The manner in which these functions are declared and invoked is
different from other functions, but they are member functions nevertheless.

The constructor and destructor functions strike at the very core of why OOP
exists. Object Oriented Programming did not become popular because it is new
and different. OOP is a better way of programming. The growing use of
computer programs in every aspect of life, combined with a growing complexity
of such programs, requires reliable programs. Airplanes loaded with passengers
landing in dense fog rely on computer programs to land the plane safely. Such
programs must works flawlessly each and every time. Sure there are many games
that people play and small bugs in games are easily tolerated. Missing a airplane
runway is another matter.

Accept the following right now about programmers. There is a difference


between what programmers know should be done, and what programmers do.
Picture the following scenario. Some data type in a program is used in 100
different, variable situations. The programmer properly initializes variables of
this data type 99 times and just happens to forget one little time. Furthermore,
keep in mind that failure to initialize a variable does not mean that the variable is
without value. It is with some unknown value. This also means that it is possible
that during the testing stage, certain unknown values assist in making the program
run correctly. Later, when the program is used in more serious applications, the
error can bring about serious consequences.

Does this lecture of doom have a connection with OOP and the special
constructors/destructor function duo? It certainly does, because these functions
will automatically take care of important programming tasks behind the scenes.
They perform a job even if programmers forget to do their job properly. Consider
the following two definitions for a constructor and destructor. Does the definition
give you a sense that these functions will make a program more reliable?

Constructor Definition

Chapter XXVII Focus on OOP, Constructors and Destructors 27.2


A constructor is a public member function of a class
that is automatically called at the instantiation of a
new object.

A constructor has no return type, including void.


A class can have more than one constructor.

Constructors are used to properly initialize a new object.

Destructor Definition

A destructor is a public member function of a class that


is automatically called when an object leaves its scope.

A destructor has no return type, including void.


A class can only have one destructor.

Destructors are used to clean-up after an object’s use.

The keywords of both definitions are: automatically called. Imagine that you
create some special functions that perform a variety of important tasks for certain
data structures. These functions should be called before the data structures are
used. Furthermore, imagine that these special functions are called automatically,
without any concern or effort on the part of the programmers. The only concern
of the programmer is to make sure the functions perform the proper actions. In a
nutshell this is what happens with constructors and destructors.

You may argue that it still is the responsibility of the programmer to write correct
constructors and destructors. A program that automatically calls bad functions is
still a bad program. This is completely correct, but remember the importance of
focus and information hiding that was discussed earlier. A programmer can start
by totally focusing on the needs of properly initializing a new object. Likewise
the programmer thinks and decides on the actions required to make a program
continue properly after an object is no longer needed. So this programmer is
totally absorbed with the constructors and destructor of a new class and after
properly testing these functions the focus can shift. This is the beauty of the

Chapter XXVII Focus on OOP, Constructors and Destructors 27.3


automatic calling. After the creation of these nifty functions, a programmer can
move on and know that important jobs are automatically done.

27.2 Scope of an Object

The introduction made such a big deal about constructors and destructors being
called automatically that it makes sense to determine just when this automatic
business takes place. This is actually easy to say, even if it sounds perhaps a little
odd. A constructor is called when an object enters its scope and a destructor is
called when an object leaves its scope. This probably makes you happy, but in
case you are still confused let us look at this process more closely.

The instance that a new object is defined, the constructor is called. This will be in
a program statement that creates a variable object of some class. The execution of
this statement is called the instantiation of the object. And during that instance,
without any special effort by the programmer, the constructor is called or invoked.

Constructor Call

The constructor is called, or invoked, during the instantiation


of a new object.

The execution of a statement, such as:


CardDeck Deck1;
is the instantiation of the object Deck1, and calls the
constructor of the CardDeck class.

There exists no visible program statement that is responsible for the calling of the
destructor function. The new object has a limited life. For the duration of the
object’s existence the destructor minds its own business and stays quiet.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.4


However, the moment that the object no longer exists - called leaving its scope -
the destructor is called.

Destructor Call

The destructor of an object is called, or invoked, when


that object leaves its scope.

There is no program statement associated with the call of


the destructor function.

If you have paid attention to previous program examples, you have seen messages
about a destructor being called at the conclusion of program execution. You may
have accepted the fact that the conclusion of a program’s execution also meant the
end of some object’s existence. At the same time, you may have wondered about
this destruction business. Time and again, reference is made to the destructor
doing some clean-up job when the object is finished. Frankly, you wonder what
the excitement is all about because the program is finished. So who cares now
anyway?

Your perception is based on program examples that really cared little about the
job of the destructor. Such program examples always defined new objects in the
main function. Leaving the main function occurred when the program finished
executing, and then the destructor was called. Such examples may have been fine
for their intended purpose, but they did little to clarify the destructor’s purpose in
life.

Program PROG2701.CPP will help with this issue. This time a total of six
objects will be created. Two objects will be created in the main function, and four
objects will be created in two separate functions. The creation of six objects will
require the destruction of six objects at some point. In other words, this program
will result in six constructor calls and six destructor calls.

For your convenience a special attribute, Number, is used to help identify the
object being constructed or destroyed. The instantiation of each new object is
performed with a constructor that uses an integer parameter. In this manner it is
possible to track each object through its existence. The destructor function takes
advantage of this information and will display the particular object that is being
destroyed. Check out the source code below and then look at the sample
execution closely.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.5


// PROG2701.CPP
// This program demonstrates the scope of an object.

#include <iostream.h>
#include <conio.h>

class Boohiss
{
private:
int Number;
public:
Boohiss(int);
~Boohiss();
};

void Module1();
void Module2();

void main()
{
clrscr();
cout << "EXECUTING MAIN FUNCTION" << endl;
Boohiss Main1(1),Main2(2);
Module1();
Module2();
cout << endl << endl;
cout << "CONTINUE IN MAIN FUNCTION" << endl;
}

Boohiss::Boohiss(int N)
{
cout << endl;
Number = N;
cout << "BOOHISS OBJECT # " << Number << " IS
CREATED" << endl;
}

Boohiss::~Boohiss()
{
cout << endl;
cout << "BOOHISS OBJECT # " << Number << " IS
DESTROYED"
<< endl;
getch();

Chapter XXVII Focus on OOP, Constructors and Destructors 27.6


}

void Module1()
{
cout << endl << endl;
cout << "EXECUTING MODULE1" << endl;
Boohiss M11(3),M12(4);
}

void Module2()
{
cout << endl << endl;
cout << "EXECUTING MODULE2" << endl;
Boohiss M21(5),M22(6);
}

PROG2701.CPP OUTPUT

EXECUTING MAIN FUNCTION

BOOHISS OBJECT #1 IS CREATED

BOOHISS OBJECT #2 IS CREATED

EXECUTING MODULE 1

BOOHISS OBJECT #3 IS CREATED

BOOHISS OBJECT #4 IS CREATED

BOOHISS OBJECT #4 IS DESTROYED

BOOHISS OBJECT #3 IS DESTROYED

EXECUTING MODULE 2

BOOHISS OBJECT #5 IS CREATED

BOOHISS OBJECT #6 IS CREATED

BOOHISS OBJECT #6 IS DESTROYED

BOOHISS OBJECT #5 IS DESTROYED

Chapter XXVII Focus on OOP, Constructors and Destructors 27.7


CONTINUE IN THE MAIN FUNCTION

BOOHISS OBJECT #2 IS DESTROYED

BOOHISS OBJECT #1 IS DESTROYED

The labels at the start of each function help to clarify the program execution. You
will note that an object leaves its scope when the function is finished. Prior to the
heading of the next function, you will note that the destructor is called. Now that
objects are created inside functions, beside the main function, the scope of an
object should make more sense.

Also note that calling destructors is done in the reverse order of object
construction. It is done in a LIFO (Last In, First Out) sequence. You have seen
this sequence before in the recursion chapter. In that chapter it was mentioned
that the stack behaves in a LIFO sequence. The stack is used to control function
execution sequence and store function information.

Destructor Calls

One destructor call is generated for every new object


that is constructed.

Multiple objects in the same scope are destroyed in a


LIFO sequence. This means that the last object created
is the first object that is destroyed.

27.3 Default Constructors

Chapter XXVII Focus on OOP, Constructors and Destructors 27.8


C++ provides default constructors for situations when a new class is declared
without any constructors. You really do not have a clue what exactly this default
constructor does. You can be pretty sure that it will not initialize the attributes of
your new object, and the job this default constructor performs is very suspect.

Now it will get a little confusing. The constructor that you declare in a class - the
one without any parameters - is also called a default constructor. Basically,
anytime that a new object is instantiated without any parameters in the picture, it
is the default constructor that is called. If you declared a constructor without
parameters, fine that will override whatever C++ had in store, otherwise you get
the C++ version of the default constructor.

Default Constructor Definition

C++ provides a default constructor during the instantiation


of a new object for a class when there is no constructor
declared in the class.

The exact nature of this C++ provided, default constructor


is unpredictable, and every class should declare
a constructor.

The constructor - without any parameters - declared by a


programmer is also called a default constructor.

You are probably not happy with this dual default constructor naming business.
It probably seems to you that some clever computer scientists were less than
clever when they cooked this up. This may be true, but there is some logic to this
naming. What exactly is default? Default is what you get when you do not
specifically indicate what you want.

This is perhaps not as strange as you may think. It is possible to declare multiple
constructors in a class. You remember from previous OOP sections that that is
the case and you have also used classes like apvector and apmatrix that allow
the construction of a new object in a variety of different ways.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.9


How does C++ keep all this multiple constructor stuff straight? It is the same
method as overloaded functions; it is done with parameter matching. There can
be many constructors, as long as they each have a unique parameter heading that
can be matched by C++ to find the proper function implementation. Now what do
you suppose happens when a new object is defined without any parameter at all?
You guessed it, you get the default constructor. And this default constructor is
either the one provided by C++, or the one that you created to override the C++
version. Either way, the constructor has no parameters, and hence is called the
default constructor.

In this section we shall investigate a variety of different constructors. Each one of


them comes without any parameters, so you now know that they shall be called
default constructors.

Program PROG2702.CPP starts by showing that the moment that you declare a
constructor, any constructor, it becomes your constructor for better or for worse.
A constructor should initialize member attributes, but in this example all you get
is a message that the constructor is called.

// PROG2702.CPP
// This program demonstrates a "default" constructor
// This constructor does not do anything but replaces
the
// "default" constructor supplied by the compiler.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck() { cout << "DEFAULT CONSTRUCTOR CALL"
<< endl; }
};

void main()
{
clrscr();
CardDeck D;

Chapter XXVII Focus on OOP, Constructors and Destructors 27.10


getch();
}

PROG2702.CPP OUTPUT

DEFAULT CONSTRUCTOR CALL

This program also provides prove that the constructor must be called by the
instantiation of the new CardDeck object, D. There are only three program
statements in the main function. You know for sure that clrscr() and getch() do
not generate any output messages. This leaves CardDeck D; as the only
candidate that can possibly be calling the constructor.

Please keep in mind that this program is a bad example for a constructor.
Constructors are called automatically, but they are not automatically good
constructors. Declaring a good constructor is the programmer’s job.

The next program example, PROG2703.CPP, shows a default constructor that


provides the minimum requirements. This constructor initializes each one of the
attributes of the CardDeck class to some logical value. Do keep in mind that the
initialization of a class does not prevent altering data member values later. The
program statement int X = 10; is not a constant and does not prevent re-assigning
a different value to X later on in the program.

// PROG2703.CPP
// This program demonstrates a constructor, which
initializes all
// the private attributes of the CardDeck class.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
};

Chapter XXVII Focus on OOP, Constructors and Destructors 27.11


void main()
{
clrscr();
CardDeck D;
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();
}

PROG2703.CPP

DEFAULT CONSTRUCTOR CALL

The previous program example used a proper constructor function, but it does not
allow any opportunity to check if the constructor did its job. The next program
will add two actions to the CardDeck class. They are the ShuffleCards and
ShowData functions.

This program example not only displays the values that were initialized by the
constructor, but it also shows that a constructor may call a member function, such
as ShuffleCards. Keep in mind, that initialization is not just a matter of assigning
values to private data members. The job of the constructor is to get the new
object ready for its intended job. It can be argued that a good automatic action -
for a CardDeck class - to perform is to shuffle the cards in the event this is
forgotten by mistake.

// PROG2704.CPP
// This program adds the ShowData and ShuffleCards
member
// functions. This program also shows how the
constructor can
// call another member function during initialization.

#include <iostream.h>

Chapter XXVII Focus on OOP, Constructors and Destructors 27.12


#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
void ShowData() const;
void ShuffleCards();
};

void main()
{
clrscr();
CardDeck D;
D.ShowData();
getch();
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
ShuffleCards();
}

void CardDeck::ShowData() const


{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
}

Chapter XXVII Focus on OOP, Constructors and Destructors 27.13


void CardDeck::ShuffleCards()
{
cout << endl << endl;
cout << "SHUFFLE CARDS FUNCTION" << endl;
}

PROG2704.CPP OUTPUT

DEFAULT CONSTRUCTOR CALL

SHUFFLE CARDS FUNCTION

SHOW DATA FUNCTION

NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1

Constructor Purpose

The main purpose of a constructor is to prepare a new object


to do its intended job correctly.

Initializing object attributes is only part of a constructor's


job. The nature of the object dictates what other actions need
to be performed.

It is common to call functions that perform necessary


preparations, and it may also be necessary to allocate
computer memory.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.14


27.4 Overloaded Constructors

Functions can be overloaded by using different function headings for functions


with the same identifier. This same principle can be applied to constructor
functions. Overloading constructors makes a lot of sense. When you define a
new object there may well be reasons for initializing an object differently from an
earlier instance. Consider our trusty CardDeck class. A new CardDeck object
for a game of Poker requires one deck. Samba-Canasta requires three decks, and
BlackJack normally needs four decks. It may well be convenient to specify this
requirement during the instantiation of the new object.

With the previously introduced apvector class you could define a new array with
the default constructor of zero elements, like apvector <int> List; At some later
point in the program a decision can be made about the size of the List array. On
the other hand, a second constructor can be called with apvector <int> List(100);
to construct List with 100 integers. It is also possible to initialize each element of
the array to zero with apvector <int> List(100,0). The apvector example
showed one default constructor and two additional constructors with parameters.

Program PROG2705.CPP shows the CardDeck class with a second constructor,


which uses two parameters to specify the number of decks used and the number of
players for a new CardDeck object. The program example defines D1 with the
default constructor, and D2 with the second, overloaded, constructor.

// PROG2705.CPP
// This program adds a second constructor that uses
parameters
// to allows optional initialization of data.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();

Chapter XXVII Focus on OOP, Constructors and Destructors 27.15


CardDeck(int,int);
void ShowData() const;
};

void main()
{
clrscr();
CardDeck D1;
D1.ShowData();
CardDeck D2(4,5);
D2.ShowData();
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

CardDeck::CardDeck(int ND, int NP)


{
cout << endl << endl;
cout << "SECOND CONSTRUCTOR CALL" << endl;
NumDecks = ND;
NumPlayers = NP;
cout << NumDecks << " card decks will be used for "
<< NumPlayers << " players" << endl;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

void CardDeck::ShowData() const


{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;

Chapter XXVII Focus on OOP, Constructors and Destructors 27.16


cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}

PROG2705.CPP

DEFAULT CONSTRUCTOR CALL

SHOW DATA FUNCTION

NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1

SECOND CONSTRUCTOR CALL


4 card decks will be used for 5 players

SHOW DATA FUNCTION

NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1

Program PROG2706.CPP adds little new information. In this program example


a third constructor is added that also enters the number of cards that will be dealt
with each hand. The logic is identical. This is just another overloaded function.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.17


The main reason for adding this example is to show that classes are not limited to
just one extra constructor, but can have a large variety of them, as long as each
constructor is clearly identified by a different formal parameter listing.

// PROG2706.CPP
// This program adds a third constructor.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
CardDeck(int,int);
CardDeck(int,int,int);
};

void main()
{
clrscr();
CardDeck D1;
CardDeck D2(2,4);
CardDeck D3(4,6,5);
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();

Chapter XXVII Focus on OOP, Constructors and Destructors 27.18


}

CardDeck::CardDeck(int ND, int NP)


{
cout << endl << endl;
cout << "2 PARAMETERS CONSTRUCTOR CALL" << endl;
NumDecks = ND;
NumPlayers = NP;
cout << NumDecks << " card decks will be used for "
<< NumPlayers << " players" << endl;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();
}

CardDeck::CardDeck(int ND, int NP, int CD)


{
cout << endl << endl;
cout << "3 PARAMETERS CONSTRUCTOR CALL" << endl;
NumDecks = ND;
NumPlayers = NP;
CardsLeft = NumDecks * 52;
CardsDealt = CD;
cout << NumDecks << " card decks will be used and "
<< CardsDealt << " cards will be dealt to "
<< NumPlayers << " players." << endl;
getch();
}

PROG2706.CPP OUTPUT

DEFAULT CONSTRUCTOR CALL

2 PARAMETERS CONSTRUCTOR CALL


2 card decks will be used for 4 players

Chapter XXVII Focus on OOP, Constructors and Destructors 27.19


3 PARAMETERS CONSTRUCTOR CALL
4 card decks will be used and 5 cards will be dealt to
6 players

Overloaded Constructor Rule

There is no specified limit on the number of overloaded


constructors for any class.

The key rule to remember is that each constructor must


have a different parameter list in the function heading.

Identical function headings with different function bodies


cannot be properly identified by C++.

27.5 Constructor Initializer List

Every constructor example has initialized private object attributes with the ( = )
assignment operator. You saw statements like NumPlayers = 4; used in the body
of a constructor function. In this section you will see a few examples that show
that there is another way to initialize object attributes. It can also be done with
the so called constructor initializer list.

When a constructor is invoked, the function first checks the initializer list, which
is placed after the function heading, if it exists. After that stage, C++ continues
executing the program statements in body of the constructor.

The Two Stages of a Constructor Call

When a constructor is invoked, C++ first checks the


initializer list, located between the function heading

Chapter XXVII Focus on OOP, Constructors and Destructors 27.20


and the function body.

Program statements, such as assignments, in the function


body are invoked secondly.

CardDeck::CardDeck() :
NumDecks(1) // initialization stage
{
NumPlayers = 4; // second stage
}

Note that the function heading requires a colon ( : ) and then any object attributes
can be initialized by using the data identifier and placing the value after the
identifier between parenthesis, like a parameter. Multiple initializations are
separated by commas. Do not use semi-colons.

// PROG2707.CPP
// This program demonstrates how the same constructor
can assign
// a value to private data in the function body, as
well as the
// initializer list.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
public:
CardDeck(int);
};

void main()
{
clrscr();
CardDeck D(2);
getch();

Chapter XXVII Focus on OOP, Constructors and Destructors 27.21


}

///////////////////////////////////////////////////////
/////////
// Constructor with function body assignment
//
CardDeck::CardDeck(int ND)
{
NumDecks = ND;
cout << "CardDeck Constructor Call" << endl;
cout << "There are " << NumDecks << " decks" <<
endl;
}

///////////////////////////////////////////////////////
//////////
// Constructor with initializer list assignment
//
//CardDeck::CardDeck(int ND)
// : NumDecks(ND)
//{
// cout << "CardDeck Constructor Call" << endl;
// cout << "There are " << NumDecks << " decks" <<
endl;
//}

PROG2707.CPP OUTPUT

CardDeck Constructor Call


There are 2 decks

It is easier to appreciate the purpose of the initializer list by looking at a variety of


different program examples. The initializer list is not simply a second approach
to an existing method. It has a unique capability that needs to be explored. You
will run into situations that the only possible option is to use an initializer list. It
is important that you realize such situations.

The next program example shows two different classes. Both the CardDeck
class and the CardGame class have constructors that use the initializer list to
assign values to the private attributes of the classes. This example is still at the

Chapter XXVII Focus on OOP, Constructors and Destructors 27.22


level where it matters little if you assign values in the constructor function body
or if you assign values in the initializer list.

// PROG2808.CPP
// This program shows two separate classes, with two
separate
// objects and two different constructors that each use
an
// initializer list.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
public:
CardDeck(int ND);
};

class CardGame
{
private:
int NumPlayers;
public:
CardGame(int NP);
};

void main()
{
clrscr();
CardDeck D(4);
cout << endl;
CardGame G(5);
getch();
}

CardDeck::CardDeck(int ND)
: NumDecks(ND)

Chapter XXVII Focus on OOP, Constructors and Destructors 27.23


{
cout << "CardDeck Constructor Call" << endl;
cout << "There are " << NumDecks << " Decks" <<
endl;
}

CardGame::CardGame(int NP)
: NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<
endl;
}

PROG2708.CPP OUTPUT

CardDeck Constructor Call


There are 4 Decks

CardGame Constructor Call


There are 5 players

Defining objects for CardDeck and CardGame separately brings few


difficulties. How about a different approach? It might make sense to use the
CardDeck type inside the CardGame declaration. Program PROG2709.CPP
has such a scenario and CardDeck has two constructors. You will see in the
program execution that the definition of a CardGame object results in calling the
default constructor of the CardDeck class. Does it make sense that the default
constructor is called? Do you see anything in the program that gives a clue what
constructor should be called? And what happens when a program does not
provide clear directions? You guessed it; you will get the default constructor.

// PROG2709.CPP
// This program uses two classes. The CardDeck class
is
// contained as a data member in the CardGame class.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.24


The CardDeck
// constructor is called before the CardGame
constructor.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
public:
CardDeck();
CardDeck(int ND);
};

class CardGame
{
private:
int NumPlayers;
CardDeck Cards;
public:
CardGame(int NP);
};

void main()
{
clrscr();
CardGame G(5);
getch();
}

CardDeck::CardDeck()
: NumDecks(0)
{
cout << "CardDeck Default Constructor Call" << endl;
cout << "There are " << NumDecks << " Decks" <<
endl;
}
CardDeck::CardDeck(int ND)
: NumDecks(ND)
{

Chapter XXVII Focus on OOP, Constructors and Destructors 27.25


cout << "CardDeck Overloaded Constructor Call" <<
endl;
cout << "There are " << NumDecks << " Decks" <<
endl;
}

CardGame::CardGame(int NP)
: NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<
endl;
}

PROG2709.CPP OUTPUT

CardDeck Default Constructor Call


There are 0 Decks
CardGame Constructor Call
There are 5 Players

You must be wondering by now what exactly the point is. So far this initializer
list business has added little to your quality of life. In the next program a small,
but significant change is made. The CardDeck class is left with only one
constructor, and that constructor requires a parameter. The program example that
you just studied showed that the default constructor was called in the absence of
any special clues to the contrary.

What will happen in this case when there is no default constructor around?
Perhaps you think that it becomes a trivial issue because there is only one
constructor left. There can be no ambiguity. There is no ambiguity but there is
also the issue of information. The only constructor left for the CardDeck class
requires some integer information. How is this integer parameter going to be
passed to the constructor? In this program example there is no information passed
and the result is an error message.

// PROG2710.CPP
// This program does not compile because a default
constructor
// cannot be found for the CardDeck class.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.26


Construction of a
// CardGame object starts by first constructing a
CardDeck object.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
public:
CardDeck(int ND);
};

class CardGame
{
private:
int NumPlayers;
CardDeck Cards;
public:
CardGame(int NP);
};

void main()
{
clrscr();
CardGame G(5);
getch();
}

CardDeck::CardDeck(int ND)
: NumDecks(ND)
{
cout << "CardDeck Overloaded Constructor Call" <<
endl;
cout << "There are " << NumDecks << " Decks" <<
endl;
}

CardGame::CardGame(int NP)
: NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<

Chapter XXVII Focus on OOP, Constructors and Destructors 27.27


endl;
}

PROG2710.CPP OUTPUT

Error PROG2710.CPP 50: Cannot find default constructor


to
initialize member ’Cards’

This is an interesting error message. C++ is not concerned about receiving some
information. It gets none, so the compiler searches for a default constructor that
will require anything special. Failing to find such a constructor, C++ responds
with the error message above.

We have finally arrived at a critical point in this initializer list progression. The
problem with the previous program is going to be solved by including information
in the definition of the CardGame object that will be passed to the constructor of
the CardDeck object. It turns out that the only way to achieve this parameter
passing is with the help of the initializer list.

// PROG2711.CPP
// This program demonstrates how to pass information to
the
// current object being constructed as well as the
constructor
// of a included object using initializer lists.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
public:
CardDeck(int ND);
};

class CardGame
{
private:

Chapter XXVII Focus on OOP, Constructors and Destructors 27.28


int NumPlayers;
CardDeck Cards;
public:
CardGame(int ND, int NP);
};

void main()
{
clrscr();
CardGame G(2,5);
getch();
}

CardDeck::CardDeck(int ND)
: NumDecks(ND)
{
cout << "CardDeck Constructor Call" << endl;
cout << "There are " << NumDecks << " Decks" <<
endl;
}

CardGame::CardGame(int ND, int NP)


: Cards(ND),NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<
endl;
}

PROG2711.CPP OUTPUT

CardDeck Constructor Call


There are 2 Decks
CardGame Constructor Call
There are 5 Players

The critical part in achieving correct results in shown below. The CardGame
constructor receives information for ND (NumDecks) and NP (NumPlayers). The
NP information goes straight to the NumPlayers attribute of the CardGame
object. The ND information is passed via a Cards(ND) constructor call to the
CardDeck constructor. The initializer list is behaving here like an object
definition.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.29


CardGame::CardGame(int ND, int NP)
: Cards(ND),NumPlayers(NP)

Reason for Using the Initializer List

Use the initializer list when a class has members of another


class type, and information needs to be passed to the
“nested” constructor.

This does not only apply to user-defined classes, but also to


existing classes like apstring, apvector and apmatrix. These
AP classes are frequently used to declare data variables
in a user-defined class.

The summary box states that initializer list business apply to situations where an
existing class, like apvector is used inside a class that you declare. We will
explore that possibility in the next two programs. Both programs use a Grades
class. It is a small class that is meant to process test grades. An apvector object,
List, is used to store the grades. A variety of member functions are listed, but
they are only implemented as stubs. The only concern we have with these
program examples is the manner in which the constructor initializes the data
member values.

The first example, PROG2712.CPP, initializes all the data members to zero,
resizes the List object to 40 elements, and assigns 0 to each array element. The
apvector resize function allows this type of approach. This method works, but as
you will see shortly, there is a better way.
// PROG2712.CPP
// This program demonstrates how to manipulate a nested class,
// which in this case is an apvector. Ignore the warnings about
// functions returning a value. All functions, but the
// constructor are implemented as stubs only. Values are
// assigned to the data members of the Grades class in the
// constructor body.

#include <iostream.h>
#include <conio.h>

Chapter XXVII Focus on OOP, Constructors and Destructors 27.30


#include "APVECTOR.H"

class Grades
{
private:
apvector <int> List; // array of class grades
int Low; // lowest grade in the list
int High; // highest grade in the list
double Mean; // average grade in the list
public:
Grades();
void EnterGrades() { }
void ShowGrades() const { }
int FindLow() { }
int FindHigh() { }
double FindMean() { }
};

void main()
{
clrscr();
Grades Period1;
getch();
}

Grades::Grades()
{
cout << "Constructor Call" << endl;
Low = 0;
High = 0;
Mean = 0;
List.resize(40);
for (int K = 0; K < List.length(); K++)
List[K] = 0;
cout << "List has " << List.length() << " elements." << endl;
}

PROG2712.CPP OUTPUT

Constructor Call
List has 40 elements

It is quite possible you see nothing wrong with the previous program example.
After all, the constructor job was accomplished. The mission was to initialize
every data member to zero and construct an apvector object with 40 elements.
Even though, List was constructed as an empty array, there was no problem
because the Grades constructor cleverly resized List to 40 elements.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.31


There is nothing wrong with the syntax of the previous program, and logically
speaking the required job is done. Now compare the two constructors. The next
program uses the exact same Grades class, and everything else is the same,
except the manner in which the constructor assigns values. This time an
initializer list is used. The output is identical for both programs.

// PROG2713.CPP
// This program uses the same Grades class as the previous
// program. In this example the initializer list is used in the
// Grades constructor to assign values to the data members.

#include <iostream.h>
#include <conio.h>
#include "APVECTOR.H"

class Grades
{
private:
apvector <int> List; // array of class grades
int Low; // lowest grade in the list
int High; // highest grade in the list
double Mean; // average grade in the list
public:
Grades();
void EnterGrades() { }
void ShowGrades() const { }
int FindLow() { }
int FindHigh() { }
double FindMean() { }
};

void main()
{
clrscr();
Grades Period1;
getch();
}

Grades::Grades() :
Low(0), High(0), Mean(0), List(40,0)
{
cout << "Constructor Call" << endl;
cout << "List has " << List.length() << " elements." << endl;
}

/ PROG2713.CPP OUTPUT

Constructor Call
List has 40 elements

Chapter XXVII Focus on OOP, Constructors and Destructors 27.32


A very significant difference is that it was not necessary to use a loop structure to
assign values to each one of the List array elements. With the aid of the trusty
initializer list it is possible to call the constructor of the nested class. In this case
this is the apvector constructor, and during construction both the size and initial
values can be specified. This convenience is not possible without using the
initializer list.

Let it never be said that Leon Schram does not know how to beat some topic to a
pulp. That is right, we are not yet finished with the initializer list. Exposure is the
title of these book, and exposure you will get, like it or not.

Program PROG2714.CPP goes one step further than the previous initializer list
examples. You just learned how to pass information to a nested constructor. That
takes care of getting information to the private attributes of a nested class. There
is also the need to call a function of a nested class. The next program example
first declares LineClass, which includes a function that draws a line. A second
class, RectangleClass contains LineClass and uses the DrawLine function inside
the DrawRectangle function. It really is no big secret at all. In such a case use
the dot.function notation that you have used before with nested records.

// PROG2714.CPP
// This program shows another example of how to pass information
// to a nested object's constructor with the initializer list.
// It also shows how to call the function of a nested object.

#include <iostream.h>
#include <conio.h>

class LineClass
{
private:
int L;
public:
LineClass(int LSize);
void DrawLine() const;
};

class RectangleClass
{
private:

Chapter XXVII Focus on OOP, Constructors and Destructors 27.33


int Width;
LineClass Length;
public:
RectangleClass(int LSize, int WSize);
void DrawRectangle() const;
};

void main()
{
clrscr();
RectangleClass RectangleObject(30,15);
RectangleObject.DrawRectangle();
getch();
}

LineClass::LineClass(int LSize)
: L(LSize)
{
}

void LineClass::DrawLine() const


{
for (int K=1; K<=L; K++)
cout << "#";
cout << endl;
}

RectangleClass::RectangleClass(int LSize, int WSize)


: Length(LSize), Width(WSize)
{
}

/////////////////////////////////////////////////////////////////
// Length is a member of RectangleClass. However, Length is an
// object of LineClass, which has DrawLine as a member function.
// Calling DrawLine then uses the normal "dot.function" notation.
//
void RectangleClass::DrawRectangle() const
{
for (int K = 1; K <= Width; K++)
Length.DrawLine();
cout << endl;
}

Chapter XXVII Focus on OOP, Constructors and Destructors 27.34


PROG2714.CPP OUTPUT

##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################

The purpose for using the initializer list has been shown. I have found that
students often wonder if it is possible to call functions in the initializer list. Not
just a constructor function, but some other function. Such a question is motivated
by the idea that this initialization stage is really identical to the function body, but
done with different syntax.

Program PROG2715.CPP should convince you that this peculiar initializer


location between function heading and function body is limited to strictly special
purposes that have been demonstrated. For experiment sake, a single class is
declared and an attempt is made to call a member function from the initialization
stage of the constructor. You will find that the program does not compile and
complains bitterly.

There is not really a need to call a member function in the initializer list. The
purpose of the initializer list is to assign values during the construction of a new
object. This construction may well include that first an object is constructed of
some nested class. This situation requires the ability to pass information to a
nested class, and this is precisely why we have an initializer list.

You will hear it stated in various places that the initializer list is also a more
efficient method to assign values. this may well be true, but personally I am not
convinced this is any significant difference. Certainly at the level that
introductory computer science student construct new objects it is not noticeable.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.35


// PROG2715.CPP
// This program attempts to call a function in the initializer
// list. This will not compile because member functions may not
// be called in an initializer list.

#include <iostream.h>
#include <conio.h>

class LineClass
{
private:
int L;
public:
LineClass(int LSize);
void DrawLine() const;
};

void main()
{
clrscr();
LineClass LineObject(20);
getch();
}

/////////////////////////////////////////////////////////////////
// Removing the comments shows that you cannot make a function
// call in an initializer list
//
LineClass::LineClass(int LSize)
// : DrawLine()
{
L = LSize;
DrawLine();
}

void LineClass::DrawLine() const


{
for (int K=1; K<=L; K++)
cout << "#";
cout << endl;
}

PROG2715.CPP OUTPUT WILL NOT COMPILE

Error PROG2715.CPP 33: ’DrawLine’ is not a non-static


data
member and can’t be initialized here

Chapter XXVII Focus on OOP, Constructors and Destructors 27.36


APCS Examination Alert

The AP classes provided by the College Board, as well


as the Big Integer case study, use initializer lists.

Frequently, information needs to be passed to nested classes,


and the use of initializer lists assures that information can
be properly passed to nested classes.

27.6 Destructors

This chapter is titled Constructors and Destructors. It does appear that


constructors have been getting the lion’s share of the intention in this chapter so
far. There is logic to that. You can only have one destructor. Destructors do not
accept parameters, and they cannot call other destructors. In other words, there is
not as much you can do with a destructor. However, the humble destructor does
deserve its very own section.

In the beginning of the chapter you saw a program that demonstrated when a
destructor is called. This object destruction business occurs whenever an object
is no longer in scope. In most practical cases this happens when the function
finishes executing that instantiated the object. But knowing when a destructor is
called does not help in answering what exactly the job of the destructor is.

In various cases it has been mentioned that a destructor cleans up the business that
was created by the new object. The destructors returns the computer to the

Chapter XXVII Focus on OOP, Constructors and Destructors 27.37


condition it was in prior to the instantiation of an object. This is all good
sounding stuff, but it lacks tangible meaning that you can appreciate.

There is once again this familiar computer science problem. The real glory of the
destructor cannot be shown until other C++ features are introduced in some later
chapters. But even without demonstrating the real practical applications of a
destructor, something can be shown right now.
Suppose you need to do some graphics in a program. Now this graphics stuff is
not everywhere in the program. It just happens in one particular function. This
means that the graphics display function needs to put the computer in graphics
mode, and when the job is done, text mode must return for the rest of the
program.

For our next program example, we will use a special class, called Circles. Its job
is to draw a bunch of concentric circles around the midpoint of the monitor. What
might be some appropriate constructor business for such a class? How about
specifying the center of the circles to be drawn? Now it also makes sense to put
the computer in the appropriate graphics mode that an object of the Circles class
requires. If the constructor makes sense now, is it also not logical then for the
destructor to return the computer to its original text mode?

This business of putting the computer in text mode is not so far fetched. Students
enjoy graphics programs, and often projects written by students finish executing
and leave the computer hanging in some type of funky graphics mode that messes
up other executions.

I mentioned earlier that the destructor has quite an important job in some areas
that will make sense later on. Let me at least try a brief explanation and give you
somewhat of a first exposure into dynamic memory allocation. You have had
some introduction to C++ pointers. Most of the pointer stuff was introduced to
help explain the nature of C-style arrays. There is a lot more to this pointer stuff.

C++ allocates necessary memory for a data type whenever a new variable is
defined. This applies to simple data types, data structures, and our good friend
the objects as well. When your program executes, the computer’s memory has all
these nicely organized sections of memory reserved for the data in your program.
In all likelihood there are chunks of memory scattered all over the memory that
could be quite useful for a program. Can this memory be accessed?

The answer to that fascinating (computer scientists must be easily fascinated)


questions is both yes and no. The answer is no if you consider the traditional data
types that you have used so far. The answer is yes, if you consider dynamic
memory allocation with pointers. It is possible to create exotic data structures that
link different memory locations together with the use of pointers. Remember that
pointers store the address of a memory location. With clever use of pointers it is

Chapter XXVII Focus on OOP, Constructors and Destructors 27.38


possible to create a very large data structure that grabs memory all over the
computer’s RAM.

Using this memory is frequently only for a limited execution phase. It is possible
that another part of the program’s execution wants its share of this memory,
which can be used during program execution. If memory is not returned for
future use, your computer rapidly has a problem. Imagine an airplane with a
faulty lavatory indicator. When all eight lavatories are occupied, the signals all
over the airplane indicate occupied. Unless some mechanism triggers these
signals back to vacant, when people are finished, nobody else can go to the
bathroom.

In a nutshell, this helps to explain what a destructor can do for you. In a situation
where an object uses memory that was acquired during program execution, the
destructor can return the memory for use by a later phase of the program
execution.

Destructor’s Purpose

The general purpose of a destructor is to clean-up after the


actions of an object, when the object is no longer in use.
Formally, this means that the destructor is invoked when
the object is no longer in scope.

The nature of this clean-up can be something like returning


the computer to its former state, such as text mode after
the object used graphics mode.

In most real-life situations, the job of the destructor revolves


around managing memory. Objects often use computer
memory that is not necessary after the object has done
its job. The destructor makes sure that this memory is
returned in such a manner that the memory can be re-used.

The Graphics Package Has Changed

Exposure C++ has gone through various stages. It is very well possible that last
year you used Graphics library that has been altered by the time you arrived to
this chapter. Currently, the individual graphics functions have been neatly

Chapter XXVII Focus on OOP, Constructors and Destructors 27.39


packaged, encapsulated is the official term, into a GFX320 class. The function
that you used last year are the same. The difference is that you need to access
each function as a member of the GFX320 class. In the event that you are not
familiar with the new GFX320 class, both the header file is shown below. It
includes the necessary comments to use the class properly. This GFX320 class
will not only be used in the next program example, but it will also be necessary
for your next program assignment.
// GFX320.H
// Header file of the GFX320 class

///////////////////////////////////////////////////////////////////////
// The header file starts with a set of comments that explains each
// of the available GFX320 functions.
///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////
// GFX320 default constructor, which initializes the 320 X 200 X 256 mode,
// sets the foreground color to white, and computes the Sin and Cos tables.
// GFX320()

///////////////////////////////////////////////////////////////////////
// GFX320 constructor, which initializes the 320 X 200 X 256 mode,
// computes the Sin and Cos tables, sets the background color BC and
// the graphics foreground color FC.
// GFX320(Byte BC, Byte FC)

///////////////////////////////////////////////////////////////////////
// Function Stop halts program execution until a key is pressed.
// This function allows viewing of intermediate graphics displays.
// void Stop()

///////////////////////////////////////////////////////////////////////
// Function SetColor sets the color (Clr) for the next GFX320 object
// to be displayed on the screen
// void SetColor(int Clr)

///////////////////////////////////////////////////////////////////////
// Function GetColor returns the current color used for displaying
// GFX320 objects.
// Byte GetColor()

///////////////////////////////////////////////////////////////////////
// Function PutPixel draws a pixel at the (X,Y) coordinate.
// void PutPixel(int X, int Y)

///////////////////////////////////////////////////////////////////////
// Function GetPixel returns the color of the pixel at coordinate (X,Y).
// Byte GetPixel(int X, int Y)

///////////////////////////////////////////////////////////////////////
// Function Line draws a line from coordinate (X1,Y1) to (X2,Y2).
// void Line(int X1, int Y1, int X2, int Y2)

///////////////////////////////////////////////////////////////////////
// Function Rectangle draws a rectangle using coordinate (X1,Y1) as
// the top-left corner and coordinate (X2,Y2) as the bottom-right corner.
// Parameter Filled determines if the Rectangle is drawn OPEN or CLOSED.
// void Rectangle(int X1, int Y1, int X2, int Y2, bool Filled)

///////////////////////////////////////////////////////////////////////
// Function Circle draws a circle at center (CX,CY) with radius R.
// void Circle(int CX, int CY, int R)

///////////////////////////////////////////////////////////////////////
// Function Ellipse draws an ellipse with center (CX,CY) with
// horizontal X-Radius XR and vertical Y-radius YR.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.40


// void Ellipse(int CX, int CY, int XR, int YR)

///////////////////////////////////////////////////////////////////////
// Function SetBackGround fills the entire screen with Clr color.
// void SetBackGround(Byte Clr)

///////////////////////////////////////////////////////////////////////
// Function FloodFill fills a graphics object with a specified color.
// Be mindful that this function will only works with small areas.
// void FloodFill(int X, int Y, , Byte OldColor, Byte NewColor)

///////////////////////////////////////////////////////////////////////
// Function MakeStar draws a five-pointed star with center (CX,CY)
// and radius R.
// void MakeStar(int CX, int CY, int R)

// const MIDX = 159; // midpoint X coordinate value


// const MAXX = 319; // maximum X coordinate value
// const MIDY = 99; // midpoint Y coordinate value
// const MAXY = 199; // maximum Y coordinate value
// const MAXC = 255; // maximum number of colors
// const bool CLOSED = true; // constant to draw filled-in rectangle
// const bool OPEN = false; // constant to draw open rectangle

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////
// Preprocessors, which prevent multiple inclusion of this file.
//
#ifndef _GFX320_H
#define _GFX320_H

#include <dos.h>
#include <conio.h>
#include <math.h>
#include "BOOL.H"
#include "APVECTOR.H"

// The remainder of this header file contains the actual source code of the
// constants used by the GFX320 class and the declaration of the GFX320 class.
// This declaration may not have made sense if you saw it before. Now that
// you have learned more about OOP, you should be able to understand the
// different parts of the GFX320 class declaration.

typedef unsigned short Byte; // byte type definition


typedef unsigned int Word; // word type definition

const MAXX = 319; // maximum X coordinate value


const MAXY = 199; // maximum Y coordinate value
const MIDX = 159; // midpoint X coordinate value
const MIDY = 99; // midpoint Y coordinate value
const MAXC = 255; // maximum number of colors

const bool CLOSED = true; // constant to draw filled-in rectangle


const bool OPEN = false; // constant to draw open rectangle

const VGA_RAM = 0xA000; // base segment address of vga memory

Chapter XXVII Focus on OOP, Constructors and Destructors 27.41


// The GFX320 class includes a large number of private member functions.
// You have not seen that feature much in previous class examples. It is
// quite typical for most classes to utilize a variety of "helper" functions that
// are meant to be strictly used by the member functions of the class.
// These member functions are not supposed to be called by any client
// program of the class, and as such should be protected from access in
// the private segment of the class.

// One good example is function MakeTables. This function is called during


// the construction of a new object and computes a set of trigonometric tables.
// These tables are computed once and are now available during the display of
// any curved graphics function like Circle and Ellipse.

class GFX320
{
private:

// private data members

apvector <float> SinTab; // sin lookup table


apvector <float> CosTab; // cos lookup table
float SinTab[630]; // sin lookup table
float CosTab[630]; // cos lookup table
float AR; // aspect ratio: make circle symmetric
float PI; // approximation of PI
Byte Color; // foreground color
Byte Mode; // graphics mode value

// private helper functions

void SetMode();
void MakeTables();
int Round(float X);
void HLine(int X1, int X2, int Y1);
void VLine(int Y1, int Y2, int X1);
int Sign(int Number);

public:
GFX320();
GFX320(Byte BC, Byte FC);
~GFX320();
void Stop();
void SetColor(int C);
Byte GetColor();
void PutPixel(int X, int Y);
Byte GetPixel(int X, int Y);
void Line(int X1, int Y1, int X2, int Y2);
void Rectangle(int X1, int Y1, int X2, int Y2, bool Filled);
void Circle(int CX, int CY, int R);
void Ellipse(int CX, int CY, int XR, int YR);
void SetBackGround(Byte Clr);
void MakeStar(int CX, int CY, int R);
void FloodFill(int X, int Y, Byte OldColor, Byte NewColor);
};

#include "GFX320.CPP"

#endif

Chapter XXVII Focus on OOP, Constructors and Destructors 27.42


Now take a look at program PROG2716.CPP, and observe the lovely job that the
constructor does putting the computer in graphics mode. Then after a bunch of
beautiful circles are drawn, the destructor takes front center and returns the
computer to text mode.

// PROG2716.CPP
// This program demonstrates one use of the destructor by
// returning the computer to the state prior to constructing the
// object. In this case the constructor places the computer in
// VGA graphics mode, and the destructor returns the computer to
// text mode.

#include <iostream.h>
#include <conio.h>
#include "GFX320.H"

void main()
{
clrscr();
cout << "STARTING IN TEXT MODE" << endl;
getch();
{
GFX320 C;
int Radius;
int Color = 8;
for (Radius=10; Radius < 100; Radius += 10, Color++)
{
C.SetColor(Color);
C.Circle(MIDX,MIDY,Radius);
}
C.Stop();
}
cout << "RETURNING TO TEXT MODE" << endl;
getch();
}

PROG2716.CPP OUTPUT

STARTING IN TEXT MODE

Chapter XXVII Focus on OOP, Constructors and Destructors 27.43


RETURNING TO TEXT MODE

27.7 Copy Constructors

Oh, and you thought we were done with constructors. No, there is more. The
destructor section was meant to give you a little relief before the grand finale of
this constructor stuff. But have heart because this will be the last section. This
section will concern itself with copy constructors.
A copy constructor takes care of the business of constructing a new object as a
copy of an existing object. This copying can be done by C++ default copy
constructors or you can take control. This is pretty much a recurring theme by
now. So first let us take a look at some of the default copying that is already
available. Program PROG2717.CPP makes a copy of an object using the
assignment ( = ) operator. This is not a case where a new object is constructed as
a copy of an existing object. In this example you will see two objects instantiated
with two different constructors. The first object then is altered to the second
object with an assignment statement.

// PROG2717.CPP
// This program shows how the assignment operator can
make a copy

Chapter XXVII Focus on OOP, Constructors and Destructors 27.44


// of an object.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
CardDeck(int,int);
void ShowData() const;
};

void main()
{
clrscr();
CardDeck D1;
D1.ShowData();
CardDeck D2(4,5);
D2.ShowData();
D1 = D2;
D1.ShowData();
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

CardDeck::CardDeck(int ND, int NP)


{

Chapter XXVII Focus on OOP, Constructors and Destructors 27.45


cout << endl << endl;
cout << "SECOND CONSTRUCTOR CALL" << endl;
NumDecks = ND;
NumPlayers = NP;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

void CardDeck::ShowData() const


{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}

PROG2717.CPP OUTPUT

DEFAULT CONSTRUCTOR CALL

SHOW DATA FUNCTION CALL

NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1

SECOND CONSTRUCTOR CALL

SHOW DATA FUNCTION

NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1

Chapter XXVII Focus on OOP, Constructors and Destructors 27.46


SHOW DATA FUNCTION

NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1

In the second program example, program PROG2718.CPP, constructs a second


object as a copy of an existing object. You will note that the definition of the
second object uses the first object as a parameter. This uses the default copy
constructor, which can be used with assignment, as well as with parameter
passing. When an object is defined with an actual parameter of an existing object,
then a copy is constructed of the existing object.

// PROG2718.CPP
// This program makes a second object a copy of the
first
// object when the second copy is constructed.

#include <iostream.h>
#include <conio.h>

class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
CardDeck(int,int);
void ShowData() const;
};

void main()
{
clrscr();

Chapter XXVII Focus on OOP, Constructors and Destructors 27.47


CardDeck D1(4,5);
D1.ShowData();
CardDeck D2(D1);
D2.ShowData();
}

CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

CardDeck::CardDeck(int ND, int NP)


{
cout << endl << endl;
cout << "SECOND CONSTRUCTOR CALL" << endl;
NumDecks = ND;
NumPlayers = NP;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}

void CardDeck::ShowData() const


{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}

PROG2718.CPP

Chapter XXVII Focus on OOP, Constructors and Destructors 27.48


SECOND CONSTRUCTOR CALL

SHOW DATA FUNCTION

NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1

SHOW DATA FUNCTION

NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1

Program PROG2719.CPP adds some additional information about the C++ copy
constructors. First, this program displays the memory location of each object.
This will demonstrate that the copy of the first object does get its own memory
location. Second, this program demonstrates that the C++ copy constructor does
not call the user created constructor. This should make sense because the
programmer’s constructor initializes the new object. Now the copy constructor
creates a new object as a copy of some existing object. C++ basically tells you to
make up your mind. Do you want to use the regular constructor or do you want to
use the copy constructor? Do not try to use both. The program output will show
only one constructor call.

// PROG2719.CPP
// This demonstrates that the copy constructor makes a
new object
// at a different memory location. It also shows that
the new
// object calls the C++ "default" constructor and not
the
// programmer's default constructor.

#include <iostream.h>
#include <conio.h>

Chapter XXVII Focus on OOP, Constructors and Destructors 27.49


class CardDeck
{
public:
CardDeck();
};

void main()
{
clrscr();
CardDeck D1;
cout << "D1 Memory Address: " << &D1 << endl;
CardDeck D2(D1);
cout << "D2 Memory Address: " << &D2 << endl;
getch();
}

CardDeck::CardDeck()
{
cout << endl;
cout << "PROGRAMMER'S CONSTRUCTOR CALLED" << endl;
}

PROG2719.CPP OUTPUT

PROGRAMMER’S CONSTRUCTOR CALLED


D1 Memory Address: 0x8f7bfff4
D2 Memory Address: 0x8f7bfff2

The next program example takes a peek at pointers, objects and constructors.
What if any of the object’s attributes are pointers? Does that present any special
requirements or problems? At first there will appear hardly any difference. You
do need to make sure that the new command is used. This command (actually an
operator) allocates the necessary memory space for a pointer data type. In other
words, a pointer to an integer gets 2 bytes, a pointer to a float gets 4 bytes, etc. by
using new. The new command will get serious attention in several future
chapters. Right now some fundamental work will need to be done with pointers
to help establish some important concepts about copy constructors.

Dynamic Memory Allocation and the new Operator

Chapter XXVII Focus on OOP, Constructors and Destructors 27.50


C++ allocates blocks of memory at compile time for
program variables, called static memory allocation
because the memory size does not change.

C++ also provides access to memory during program


execution, called dynamic memory allocation, because
memory changes during program execution.

The new operator is used to access memory dynamically.


The examples below allocate 2 bytes for an integer and
8 bytes for a real number.

Int *IntPtr; // pointer to int type


double *DoublePtr; // pointer to double type
IntPtr = new int; // gets 2 bytes from RAM
DoublePtr = new double; // gets 8 bytes from RAM

In program PROG2720.CPP a Pointers class has two attributes, which are both
pointer data types. One is a pointer to an int, and the other a pointer to a float.
Keep in mind that a pointer stores an address of a memory location. It is also
possible to dereference a pointer. In this program an integer and a floating
number are assigned to the location that is stored by the two pointer variables.

It may seem like a mighty complex way to store two simple pieces of information.
Right now that is intentional. I am trying very hard to work towards the
justification of creating your own copy constructors, which is only a few
programs down the road.

// PROG2720.CPP
// This program demonstrates using a copy constructor
with
// an object that stores pointers for data variables.

#include <iostream.h>
#include <conio.h>

Chapter XXVII Focus on OOP, Constructors and Destructors 27.51


class Pointers
{
private:
int *IntPtr;
float *FloatPtr;
public:
Pointers();
void ShowData(int);
};

void main()
{
clrscr();
Pointers P1;
P1.ShowData(1);
Pointers P2(P1);
P2.ShowData(2);
}

Pointers::Pointers()
{
IntPtr = new int;
FloatPtr = new float;
*IntPtr = 12345;
*FloatPtr = 3.14159;
}

void Pointers::ShowData(int N)
{
cout << endl << endl;
cout << "Object P" << N << endl;
cout << "&P" << N << " " << this <<
endl;
cout << "IntPtr " << IntPtr << endl;
cout << "FloatPtr " << FloatPtr << endl;
cout << "*IntPtr " << *IntPtr << endl;
cout << "*FloatPtr " << *FloatPtr << endl;
getch();
}

PROG2720.CPP OUTPUT

Chapter XXVII Focus on OOP, Constructors and Destructors 27.52


Object P1
&P1: 0x8f45fff2
IntPtr: 0x8f45128e
FloatPtr: 0x8f451296
*IntPtr: 12345
*FloatPtr: 3.14159

Object P2
&P2: 0x8f45ffee
IntPtr 0x8f45128e
FloatPtr 0x8f451296
*IntPtr 12345
*FloatPtr 3.14159

Objects P1 and P2 show different memory locations, just like the previous
program example. On the other hand, the object attributes, which are pointers,
show the same identical memory locations. This is logical and should not be
seem surprising. The copy constructor creates a new object at a different memory
location from the first object. Next comes the job of copying every member of
the source object unto the target object. Since the source object contains memory
addresses, its copy, the target object will get the same memory addresses.

The time has come to see what this is leading up to. Using the C++ default copy
constructors can lead to some serious problems. If an object has some business
with pointers, it is a sure bet that the C++ copy constructors will not do its
expected job properly every time. Why? Isn’t this simply a matter of cranking
out a new object with the same values as the previous object? Yes, that is true,
but pointers march to their own drum and problems can arise.

Picture the following situation. You have two objects. One object is constructed
as a copy of the first object. The objects have attributes that are pointers. An
attribute of one object changes the value at a memory location that an attribute of
another objects also points at. Is this right? Should a change in one object’s
attribute also alter the value in another object’s attribute? It can happen, and it is
the plot of program PROG2721.CPP.

The process of the next program example involves two objects of the Pointers
class. Object P1 is constructed first and its data is displayed. Object P2 is then
constructed using P1 as a parameter. C++ will employ its default copy
constructor to make a member-for-member copy of the original P1 object. The
display of P2 data shows that the copy is indeed identical to the original.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.53


There is also an AlterData function that can modify the private attributes of a
Pointers object. This member function is called to alter the data of P2. New data
is passed to P2 and now the data of both objects is displayed again. You will note
that both objects display identical values. Should copy constructors behave in this
manner?

// PROG2721.CPP
// This program demonstrates using the default copy
constructor
// to copy an object that stores pointers for data
variables.
// The C++ copy constructor causes problems because a
change in
// one object also alters the second object.

#include <iostream.h>
#include <conio.h>

class Pointers
{
private:
int *IntPtr;
float *FloatPtr;
public:
Pointers();
void AlterData(int, float);
void ShowData(int) const;
};

void main()
{
clrscr();
Pointers P1;
P1.ShowData(1);
Pointers P2(P1);
P2.ShowData(2);
P2.AlterData(9999,9.876);
P2.ShowData(2);
P1.ShowData(1);
}

Chapter XXVII Focus on OOP, Constructors and Destructors 27.54


Pointers::Pointers()
{
IntPtr = new int;
FloatPtr = new float;
*IntPtr = 12345;
*FloatPtr = 3.14159;
}

void Pointers::AlterData(int IP, float FP)


{
*IntPtr = IP;
*FloatPtr = FP;
}

void Pointers::ShowData(int N) const


{
cout << endl << endl;
cout << "Object P" << N << endl;
cout << "*IntPtr " << *IntPtr << endl;
cout << "*FloatPtr " << *FloatPtr << endl;
getch();
}

PROG2721.CPP OUTPUT

Object P1
*IntPtr 12345
*FloatPtr 3.14159

Object P2
*IntPtr 12345
*FloatPtr 3.14159

Object P2
*intPtr 9999
*FloatPtr 9.876

Object P1
*IntPtr 9999
*FloatPtr 9.876

Chapter XXVII Focus on OOP, Constructors and Destructors 27.55


Let us keep it simple right now. Forget objects and copy constructors. Suppose
you have a program segment like the one shown below. There are two integer
variables. L is defined and initialized with the same value as K. Then L is
altered and assigned the value 200. What do you expect the output to be?

int K = 100;
int L = K;
L = 200;
cout << ”K = ” << K << endl;
cout << ”L = ” << L << endl;

Hopefully, every student and person reading this chapter will select the output
that is shown below. I do know that a few smart students will state that there will
be no output because it is a program segment that cannot compile. The point is
that a change in variable L will not - and should not - alter the value of variable
K. The fact that L started out as a copy of K is totally besides the point.

K = 100
L = 200

You see that is precisely what is wrong with the copy constructor example of
program PROG2721.CPP. The constructed copy appears fine, but then it
becomes weird when a change in one object also alters the data of another object.
Now you are not surprised by this result. It seems that attributes in both objects
reference the same memory location and a change in one object’s attributes will
automatically brings about a change in the other object’s attributes. It probably
looks like a bunch of reference parameter passing to you. Well, this is also lovely
but we are trying to copy objects and not demonstrate reference parameter
passing. The default copy constructor does something wrong. A change in one
object should not alter the second object.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.56


This situation requires that you, the programmer, take control. You cannot let the
default copy constructor handle sensitive pointer issues. The solution is to create
your very own copy constructor that will make copies without problems. The
next program is identical to the previous program, except it includes a new, and
improved, user-created copy constructor. The program runs fine now without any
undesirable side effects.

// PROG2722.CPP
// This program introduces a user-created copy
constructor.
// The program now copies the object without any side
effects.

#include <iostream.h>
#include <conio.h>

class Pointers
{
private:
int *IntPtr;
float *FloatPtr;
public:
Pointers();
Pointers(const Pointers &); // copy constructor
void AlterData(int, float);
void ShowData(int) const;
};
void main()
{
clrscr();
Pointers P1;
P1.ShowData(1);
Pointers P2(P1);
P2.ShowData(2);
P2.AlterData(9999,9.876);
P2.ShowData(2);
P1.ShowData(1);
}

Pointers::Pointers()
{
IntPtr = new int;
FloatPtr = new float;
*IntPtr = 12345;

Chapter XXVII Focus on OOP, Constructors and Destructors 27.57


*FloatPtr = 3.14159;
}

Pointers::Pointers(const Pointers &Obj)


{
IntPtr = new int;
FloatPtr = new float;
*IntPtr = *(Obj.IntPtr);
*FloatPtr = *(Obj.FloatPtr);
}

void Pointers::AlterData(int IP, float FP)


{
*IntPtr = IP;
*FloatPtr = FP;
}

void Pointers::ShowData(int N) const


{
cout << endl << endl;
cout << "Object P" << N << endl;
cout << "*IntPtr " << *IntPtr << endl;
cout << "*FloatPtr " << *FloatPtr << endl;
getch();
}

PROG2722.CPP OUTPUT

Object P1
*IntPtr 12345
*FloatPtr 3.14159

Object P2
*IntPtr 12345

Chapter XXVII Focus on OOP, Constructors and Destructors 27.58


*FloatPtr 3.14159

Object P2
*IntPtr 9999
*FloatPtr 9.876

Object P1
*intPtr 12345
*FloatPtr 3.14159

The copy constructor looks just like an overloaded constructor that uses a
parameter. A quick clue that this constructor is a copy constructor is to check the
type of the parameter. When you see a parameter of the same type as the class,
Pointers¸ you are looking at a copy constructor.

Pointers::Pointers(const Pointers &Obj)


{
IntPtr = new int;
FloatPtr = new float;
*IntPtr = *(Obj.IntPtr);
*FloatPtr = *(Obj.FloatPtr);
}

The identifiers on the left side, IntPtr, are the identifiers of the new object being
constructed. These attributes find their proper place courtesy of the this pointer.
Inside the body of the copy constructor, the first job is to allocate new space for
an integer variable and a float variable. The next job is to dereference the pointers
and copy the data values to the newly allocated integer and float space.

The dereferencing may require some explanation. Obj is not a pointer and cannot
be dereferenced. IntPtr and FloatPtr are pointers, but they must be accessed as
members of the Obj object. A program statement like (*this).Age dereferences
the this pointer. A statement like *this.Age dereferences Age because the period
operator has a higher precedence than an asterisk. This means that *Obj.IntPtr
will dereference IntPtr correctly. I do prefer using *(Obj.IntPtr) to avoid
confusion.

The true nature of the copy constructor has only been partially shown. Pointer
variables have some special capabilities to create large, complicated data
structures. These complicated data structures efficiently use whatever memory is
available and the different elements of the data structure are linked together with
some special pointer manipulation. The logic and syntax of such data structures

Chapter XXVII Focus on OOP, Constructors and Destructors 27.59


will need to wait for a later chapter. Right now you should realize that such
exotic data structures can be easily altered or completely destroyed without the
use of a proper copy constructor.

Consider this analogy about pointers. You have important papers and money in a
bank safety deposit box. At home you have an envelop with the bank name,
address, safety box number, and key. If the envelop with safety deposit
information is lost or altered, the items inside the safety box may not change, but
access to these items has been eliminated.

Shallow and Deep Copies

The C++ default copy constructor makes an item-for-item


copy of every member of the source object to the target
object. Such a copy is called a shallow copy.

Your user-created copy constructor should also make


an item-by-item copy of the source object. Additionally,
you need to copy the information stored at any memory
location that is referenced by any object attribute, and
allocate the necessary memory for the target object.
Such a copy is called a deep copy.

Copy Constructor Notes

The C++ default copy constructor makes an item-for-item

Chapter XXVII Focus on OOP, Constructors and Destructors 27.60


copy of the source object. This copy technique is appropriate
for objects that do not use any pointer attributes.

User-created copy constructors need to insure that any copy


made of a source object will properly copy all its members,
and all the information that the object points to as well.

This means that new memory needs to be allocated for


any data types that use pointers.

Pointers::Pointers(const Pointers &Obj)


{
IntPtr = new int;
*IntPtr = *(Obj.IntPtr);
}

27.8 Objects and Parameters

Several more program examples need to be shown before this chapter comes to a
close. Program PROG2723.CPP will demonstrate the difference between a
value and a reference parameter when using objects. The general parameter
passing rule is that a value parameter makes a copy of the actual parameter's
value. A reference parameter receives the address of the actual parameter.
Normally, we cannot detect when a C++ destructor is called. However, we can
observe how many times the destructor is called by placing an output statement in
the destructor function.

In the next program example two identical functions are called. One function
uses pass-by-value and the other function uses pass-by-reference. The
significance of this program is to help explain why it is important to pass objects
by reference for copy constructors specifically and functions in general. This
program shows that one more copy is made unnecessarily. This extra copy also
requires one more destructor call to be made as well.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.61


// PROG2723.CPP
// This program demonstrates the different number of objects that
// are copied when objects are passed as parameters.

#include <iostream.h>
#include <conio.h>

class Pointers
{
public:
Pointers();
~Pointers();
};

/////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by reference.
void MakeCopy1(const Pointers &P1)
{
cout << endl;
cout << "IN MAKE-COPY1" << endl;
Pointers P2(P1);
}

/////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by value.
void MakeCopy2(Pointers P1)
{
cout << endl;
cout << "IN MAKE-COPY2" << endl;
Pointers P3(P1);
}

void main()
{
clrscr();
cout << "IN MAIN FUNCTION" << endl;
Pointers P1;
MakeCopy1(P1);
MakeCopy2(P1);
cout << endl << "IN MAIN FUNCTION" << endl;
}

Pointers::Pointers()
{
cout << endl;
cout << "OBJECT IS CREATED" << endl;
}

Pointers::~Pointers()

Chapter XXVII Focus on OOP, Constructors and Destructors 27.62


{
cout << endl;
cout << "OBJECT IS DESTROYED" << endl;
getch();
}

PROG2723.CPP OUTPUT

IN MAIN FUNCTION

OBJECT IS CREATED

IN MAKE-COPY1

OBJECT IS DESTROYED

IN MAKE-COPY2

OBJECT IS DESTROYED

OBJECT IS DESTROYED

IN MAIN FUNCTION

OBJECT IS DESTROYED

The program execution gives the odd impression that there are many more
destructor calls than there are constructor calls. Let us trace through the program
execution and see what is happening.

In the main function P1 is constructed and the default constructor is called.


Function MakeCopy1 is called and object information is passed by reference.
Locally, P2 is constructed with the help of the default copy constructor. The
evidence that an object has been constructed is that the destructor is called.

Now we move on to function MakeCopy2. This time the object information is


passed by value. Locally, P3 is constructed, once again with the copy
constructor. Now there are two calls to the destructor. This is evidence that two
objects had to be constructed. The first object was constructed when the object
was passed by value. You did not see a call to a constructor because the default
copy constructor is called in this case. A second invisible call to the copy
constructor is performed and then both objects are destroyed.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.63


Finally, back in the main function, the original object still needs to be destroyed
before program execution is finished. Trace slowly through the program
execution and make sure that this makes sense to you.
The next program example calls all the same constructors and the same
destructors. The only difference from the previous program is that now the
memory location of each object is displayed. This helps to identify which objects
are copies and which object use the same address reference.

// PROG2724.CPP
// This program demonstrates the different number of objects that
// are copied when objects are passed as parameters.
// Additionally, the memory location of each object is displayed.

#include <iostream.h>
#include <conio.h>

class Pointers
{
public:
Pointers();
~Pointers();
};

////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by reference.
void MakeCopy1(Pointers &P1)
{
cout << endl;
cout << "IN MAKE-COPY1" << endl;
cout << "P1 Address: " << &P1 << endl;
Pointers P2(P1);
cout << "P2 Address: " << &P2 << endl;
}

////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by value.
void MakeCopy2(Pointers P1)
{
cout << endl;
cout << "IN MAKE-COPY2" << endl;
cout << "P1 Address: " << &P1 << endl;
Pointers P3(P1);
cout << "P3 Address: " << &P3 << endl;
}

void main()
{
clrscr();
cout << "IN MAIN FUNCTION" << endl;

Chapter XXVII Focus on OOP, Constructors and Destructors 27.64


Pointers P1;
cout << "P1 Address: " << &P1 << endl;
MakeCopy1(P1);
MakeCopy2(P1);
cout << endl << "IN MAIN FUNCTION" << endl;
}

Pointers::Pointers()
{
cout << endl;
cout << "OBJECT IS CREATED" << endl;
}

Pointers::~Pointers()
{
cout << endl;
cout << "OBJECT IS DESTROYED" << endl;
getch();
}

PROG2724.CPP

IN MAIN FUNCTION

OBJECT IS CREATED
P1 Address: 0x8fab0ffe

IN MAKE-COPY1

OBJECT IS CREATED
P1 Address: 0x8fab0ffe
P2 Address: 0x8fab0ff2

OBJECT IS DESTROYED

IN MAKE-COPY2
P1 Address: 0x8fab0ffc
P3 Address: 0x8fab0ff4

OBJECT IS DESTROYED

OBJECT IS DESTROYED

IN MAIN FUNCTION

OBJECT IS DESTROYED

Chapter XXVII Focus on OOP, Constructors and Destructors 27.65


P1 in MakeCopy1 shows the same address as P1 from the main function. This
should make sense because the main function object was passed by reference to
the first function. P2 has a different address because it is a copy of P1.

P1 in MakeCopy2 has a different address as P1 in the main function. The second


function call uses pass by value, and that means that a copy is made of the main
function object. P3 also has a different address because it is a copy of P1.

In the last program example something will be done about having more calls to
the destructor than the constructor. This time a user-created copy constructor is
added. This user-created constructor includes an output statement that allows
detection anytime that this copy constructor is called.

// PROG2725.CPP
// This program adds a user-created copy constructor to the
// previous program to help clarify when objects are copied and
// when they are not copied.

#include <iostream.h>
#include <conio.h>

class Pointers
{
public:
Pointers();
Pointers(const Pointers &Obj);
~Pointers();
};

////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by reference.
void MakeCopy1(Pointers &P1)
{
cout << endl;
cout << "IN MAKE-COPY1" << endl;
cout << "P1 Address: " << &P1 << endl;
Pointers P2(P1);
cout << "P2 Address: " << &P2 << endl;
}

////////////////////////////////////////////////////////////////
// This function makes a copy of P1 that is passed by value.
void MakeCopy2(Pointers P1)
{
cout << endl;

Chapter XXVII Focus on OOP, Constructors and Destructors 27.66


cout << "IN MAKE-COPY2" << endl;
cout << "P1 Address: " << &P1 << endl;
Pointers P3(P1);
cout << "P3 Address: " << &P3 << endl;
}

void main()
{
clrscr();
cout << "IN MAIN FUNCTION" << endl;
Pointers P1;
cout << "P1 Address: " << &P1 << endl;
MakeCopy1(P1);
MakeCopy2(P1);
cout << endl << "IN MAIN FUNCTION" << endl;
}

Pointers::Pointers()
{
cout << endl;
cout << "OBJECT IS CREATED" << endl;
}

///////////////////////////////////////////////////////////////
// User-Created Copy Constructor
Pointers::Pointers(const Pointers &Obj)
{
cout << endl;
cout << "COPY CONSTRUCTOR CALLED" << endl;
}

Pointers::~Pointers()
{
cout << endl;
cout << "OBJECT IS DESTROYED" << endl;
getch();
}

PROG2725.CPP

IN MAIN FUNCTION

OBJECT IS CREATED
P1 Address: 0x8fab0ffe

IN MAKE-COPY1

P1 Address: 0x8fab0ffe

Chapter XXVII Focus on OOP, Constructors and Destructors 27.67


COPY CONSTRUCTOR CALLED
P2 Address: 0x8fab0ff2

OBJECT IS DESTROYED

COPY CONSTRUCTOR CALLED

IN MAKE-COPY2
P1 Address: 0x8fab0ffc

COPY CONSTRUCTOR CALLED


P3 Address: 0x8fab0ff4

OBJECT IS DESTROYED

OBJECT IS DESTROYED

IN MAIN FUNCTION

OBJECT IS DESTROYED

The user-created copy constructor does not seem to copy anything. It only
displays a message. That is fine because our only interest is in identifying which
constructor is called. There is some interesting observation that can be made.

First, there are now the same number of constructor calls as there are destructor
calls. This make us feel good. Everybody wants order in the universe.

Second, there is evidence that passing an object by value generates a call to a


copy constructor. In the past - before we had a user-created copy constructor - we
knew that some object was constructed due to a destructor call. We just were not
so sure which constructor was doing something. Now we did know that it was not
the user-created constructor because there was not an appropriate message.

We have also evidence that it is better to pass objects by reference. In this case it
is not just a matter of saving the execution time required to call a copy
constructor, it is also time required to call the destructor function.

Perhaps you think that this is all pretty trivial stuff, and frankly at the level that
this is shown it is really trivial. Do keep in mind that in the real world of
industrial programming it may well be another story and a large quantity of
repeated function calls can start to slow down program execution.

Chapter XXVII Focus on OOP, Constructors and Destructors 27.68


Copy Constructor Notes

C++ provides a default copy constructor that makes only


member-for-member object copies. Serious side effects
may result with this default copy constructor when objects
contain pointer attributes.

User-created copy constructors are declared to insure that


objects are copied completely and correctly.

A copy constructor is a constructor and uses the same


syntax as any other constructor.

A copy constructor is identified by its formal parameter,


which passes an object of the same class as the constructor.

Copy constructors are called when a new object is defined


using an existing object as parameter like:

Animal Aardvark;
Animal Zebra(Aardvark);

Chapter XXVII Focus on OOP, Constructors and Destructors 27.69


A copy constructor is also called when an object is passed
by value to a function. This type of copying should be
avoided. An object parameter should be passed as a
reference const parameter.

Pointers::Pointers(const Pointers &Obj)


{
IntPtr = new int;
*IntPtr = *(Obj.IntPtr);
}

Chapter XXVII Focus on OOP, Constructors and Destructors 27.70

You might also like