| 1.124 Lecture 4 | 9/22/1998 |
Let's take a closer look at how constructors and destructors work.
Point();
// The default constructor.
Point(float fX, float fY);
// A constructor that takes two floats.
Point(const Point& p);
// The copy constructor.
~Point();
// The destructor.
These constructors can be respectively invoked by object definitions such as
Point a;
Point b(1.0, 2.0);
Point c(b);
The default constructor, Point(), is so named because it can be invoked without any arguments. In our example, the default constructor initializes the Point to (0,0). The second constructor creates a Point from a pair of coordinates of type float. Note that we could combine these two constructors into a single constructor which has default arguments:
Point(float fX=0.0, float fY=0.0);
The third constructor is known as a copy constructor since it creates one Point from another. The object that we want to clone is passed in as a constant reference. Note that we cannot pass by value in this instance because doing so would lead to an unterminated recursive call to the copy constructor. In this example, the destructor does not have to perform any clean-up operations. Later on, we will see examples where the destructor has to release dynamically allocated memory.
Constructors and destructors can be triggered more often than you may
imagine. For example, each time a Point is passed to a function
by value, a local copy of the object is created. Likewise, each time
a Point is returned by value, a temporary copy of the object is
created in the calling program. In both cases, we will see an extra
call to the copy constructor, and an extra call to the destructor.
You are encouraged to put print statements in every constructor and in
the destructor, and then carefully observe what happens.
point.h
// Declaration of class Point.
#ifndef _POINT_H_
#define _POINT_H_
#include <iostream.h>
class Point {
// The state of a Point object. Property
variables are typically
// set up as private data members, which are
read from and
// written to via public access methods.
private:
float mfX;
float mfY;
// The behavior of a Point object.
public:
Point();
// The default constructor.
Point(float fX, float fY);
// A constructor that takes two floats.
Point(const Point& p);
// The copy constructor.
~Point();
// The destructor.
void print() {
// This function will be made inline by default.
cout << "(" <<
mfX << "," << mfY << ")" << endl;
}
void set_x(float fX);
float get_x();
void set_y(float fX);
float get_y();
};
#endif // _POINT_H_
point.C
// Definition class Point.
#include "point.h"
// A constructor which creates a Point object at (0,0).
Point::Point() {
cout << "In constructor Point::Point()"
<< endl;
mfX = 0.0;
mfY = 0.0;
}
// A constructor which creates a Point object from two
// floats.
Point::Point(float fX, float fY) {
cout << "In constructor Point::Point(float
fX, float fY)" << endl;
mfX = fX;
mfY = fY;
}
// A constructor which creates a Point object from
// another Point object.
Point::Point(const Point& p) {
cout << "In constructor Point::Point(const
Point& p)" << endl;
mfX = p.mfX;
mfY = p.mfY;
}
// The destructor.
Point::~Point() {
cout << "In destructor Point::~Point()" <<
endl;
}
// Modifier for x coordinate.
void Point::set_x(float fX) {
mfX = fX;
}
// Accessor for x coordinate.
float Point::get_x() {
return mfX;
}
// Modifier for y coordinate.
void Point::set_y(float fY) {
mfY = fY;
}
// Accessor for y coordinate.
float Point::get_y() {
return mfY;
}
point_test.C
// Test program for the Point class.
#include "point.h"
int main() {
Point a;
Point b(1.0, 2.0);
Point c(b);
// Print out the current state of all objects.
a.print();
b.print();
c.print();
b.set_x(3.0);
b.set_y(4.0);
// Print out the current state of b.
cout << endl;
b.print();
return 0;
}
Until now, we have only considered situations in which the exact number of objects to be created is known at compile time. This is rarely the case in real world software. A web-browser cannot predict in advance how many image objects it will find on a web page. What is needed, therefore, is a way to dynamically create and destroy objects at run time. C++ provides two operators for this purpose:
a = new Point();
b = new Point(2.0, 3.0);
Object arrays can be allocated using statements such as
c = new Point[num_points];
In either case, new returns the starting address of the memory it has allocated, so a, b, and c must be defined as pointer types, Point *. A single object can be released using a statement such as
delete a;
When releasing memory associated with an array, it is important to remember to use the following notation:
delete[] c;
If the square brackets are omitted, only the first object in the array
will be released, and the memory associated with the rest of the objects
will be leaked.
nd_test.C
// Test program for the new and delete operators.
#include "point.h"
int main() {
int num_points;
Point *a, *b, *c;
float d;
// Allocate a single Point object in heap memory.
This invokes the default constructor.
a = new Point();
// This invokes a constructor that has two arguments.
b = new Point(2.0, 3.0);
// Print out the two point objects.
cout << "Here are the two Point objects
I have created:" << endl;
a->print();
b->print();
// Destroy the two Point objects.
delete a;
delete b;
// Now allocate an array of Point objects in heap
memory.
cout << "I will now create an array of
Points. How big shall I make it? ";
cin >> num_points;
c = new Point[num_points];
for (int i = 0; i < num_points; i++) {
d = (float)i;
c[i].set_x(d);
c[i].set_y(d + 1.0);
}
// Print out the array of point objects.
cout << "Here is the array I have created:"
<< endl;
for (int i = 0; i < num_points; i++) {
c[i].print();
}
// Destroy the array of Point objects.
delete[] c;
// What happens if [] is omitted?
return 0;
}
There are three fundamental ways of using memory in C and C++.
The following program illustrates various uses of memory. Note
that the static object in the function foo() is only allocated
once, even though foo() is invoked multiple times.
sl_test.C
// Test program for scope and the lifetime of objects.
#include "point.h"
Point a(1.0, 2.0); // Resides in static memory.
void foo() {
static Point a;
// Resides in static memory.
a.set_x(a.get_x() + 1.0);
a.print();
}
int main() {
Point a(4.0, 3.0);
// Resides in automatic memory.
a.print();
::a.print();
for (int i = 0; i < 3; i++)
foo();
Point *b = new Point(5.0, 6.0);
// Resides in heap memory.
b->print();
delete b;
// Curly braces serve as scope delimiters.
{
Point a(7.0, 9.0);
// Resides in automatic memory.
a.print();
::a.print();
}
return 0;
}
Here is the output from the program:
In constructor Point::Point(float fX, float fY)
<-- Global object a.
In constructor Point::Point(float fX, float fY)
<-- Local object a.
(4,3)
(1,2)
In constructor Point::Point()
<-- Object a in foo().
(1,0)
(2,0)
(3,0)
In constructor Point::Point(float fX, float fY)
<-- Object *b.
(5,6)
In destructor Point::~Point()
<-- Object *b.
In constructor Point::Point(float fX, float fY)
<-- Second local object a.
(7,9)
(1,2)
In destructor Point::~Point()
<-- Second local object a.
In destructor Point::~Point()
<-- Local object a.
In destructor Point::~Point()
<-- Object a in foo().
In destructor Point::~Point()
<-- Global object a.
pa_test.C
// Pointer array test program.
#include "point.h"
int main() {
int i, max_points;
Point **a;
max_points = 5;
// Create an array of pointers to Point objects.
We will use the
// array elements to hold on to dynamically allocated
Point objects.
a = new Point *[max_points];
// Now create some point objects and store them
in the array.
for (i = 0; i < max_points; i++)
a[i] = new Point(i, i);
// Let's suppose we want to eliminate the middle
Point.
i = (max_points-1) / 2;
delete a[i];
a[i] = NULL;
// Print out the remaining Points.
for (i = 0; i < max_points; i++) {
if (a[i])
a[i]->print();
}
// Delete the remaining Points. Note that
it is acceptable to pass a NULL
// pointer to the delete operator.
for (i = 0; i < max_points; i++)
delete a[i];
// Now delete the array of pointers.
delete[] a;
return 0;
}