Professional Documents
Culture Documents
Chapter XXVII
Chapter XXVII
27.6 Destructors
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.
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
Destructor Definition
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
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
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.
Destructor Call
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.
#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();
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 MODULE 1
EXECUTING MODULE 2
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
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.
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.
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;
PROG2702.CPP OUTPUT
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.
// 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();
};
CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();
}
PROG2703.CPP
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>
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();
}
PROG2704.CPP OUTPUT
NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1
Constructor Purpose
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.
// 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();
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;
}
PROG2705.CPP
NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1
NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1
// 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();
PROG2706.CPP OUTPUT
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.
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();
///////////////////////////////////////////////////////
/////////
// 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
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
// 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)
CardGame::CardGame(int NP)
: NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<
endl;
}
PROG2708.CPP OUTPUT
// PROG2709.CPP
// This program uses two classes. The CardDeck class
is
// contained as a data member in the CardGame class.
#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)
{
CardGame::CardGame(int NP)
: NumPlayers(NP)
{
cout << "CardGame Constructor Call" << endl;
cout << "There are " << NumPlayers << " Players" <<
endl;
}
PROG2709.CPP OUTPUT
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.
#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" <<
PROG2710.CPP OUTPUT
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:
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;
}
PROG2711.CPP OUTPUT
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.
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>
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.
// 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
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:
void main()
{
clrscr();
RectangleClass RectangleObject(30,15);
RectangleObject.DrawRectangle();
getch();
}
LineClass::LineClass(int LSize)
: L(LSize)
{
}
/////////////////////////////////////////////////////////////////
// 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;
}
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
##############################
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.
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.
#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();
}
27.6 Destructors
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
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?
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
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
///////////////////////////////////////////////////////////////////////
// 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.
///////////////////////////////////////////////////////////////////////
// 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)
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// 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.
class GFX320
{
private:
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
// 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
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
#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;
}
PROG2717.CPP OUTPUT
NumDecks: 1
NumPlayers: 1
CardsLeft: 52
CardsDealt: 1
NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1
NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1
// 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();
CardDeck::CardDeck()
{
cout << endl << endl;
cout << "DEFAULT CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
}
PROG2718.CPP
NumDecks: 4
NumPlayers: 5
CardsLeft: 208
CardsDealt: 1
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>
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
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.
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>
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
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.
// 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);
}
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
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.
// 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;
PROG2722.CPP OUTPUT
Object P1
*IntPtr 12345
*FloatPtr 3.14159
Object P2
*IntPtr 12345
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.
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
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.
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.
#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()
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.
// 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;
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
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;
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
OBJECT IS DESTROYED
IN MAKE-COPY2
P1 Address: 0x8fab0ffc
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.
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.
Animal Aardvark;
Animal Zebra(Aardvark);