Why Do You Build Me Up, Just toTear Me Down, Baby? - Introduction to Classes - C++ For Dummies (2014)

C++ For Dummies (2014)

Part III

Introduction to Classes

Chapter 15

“Why Do You Build Me Up, Just toTear Me Down, Baby?”

In This Chapter

arrow Creating and destroying objects

arrow Declaring constructors and destructors

arrow Invoking constructors and destructors

Objects in programs are built and scrapped just like objects in the real world. If the class is to be responsible for its well-being, it must have some control over this process. As luck would have it (I suppose some planning was involved as well), C++ provides just the right mechanism. But first, a discussion of what it means to create an object.

Creating Objects

Some people get a little sloppy in using the terms class and object. What’s the difference? What’s the relationship?

I can create a class Dog that describes the relevant properties of man’s best friend. At my house, we have two dogs. Thus, my single class Dog has two instances, Jack and Scruffy. (Well, I think there are two instances — I haven’t seen Scruffy in a few days.)

image A class describes a type of thing. An object is one of those things. An object is an instance of a class. There is only one class Dog, no matter how many dogs I have.

Objects are created and destroyed, but classes simply exist. My pets come and go, but the class Dog (evolution aside) is perpetual.

Different types of objects are created at different times. Global objects are created when the program first begins execution. Local objects are created when the program encounters their declaration.

image A global object is one that is declared outside a function. A local object is one that is declared within a function and is, therefore, local to the function. In the following example, the variable me is global, and the variable notMe is local to the function pickOne():

int me = 0;
void pickOne()
{
int notMe;
}

image According to the rules, global objects are initialized to all zeros when the program starts executing. Objects declared local to a function have no particular initial value. Having all data members have a random state may not be a valid condition for all classes.

C++ allows the class to define a special member function that is invoked automatically when an object of that class is created. This member function, called the constructor, initializes the object to a valid initial state. In addition, the class can define a destructor to handle the destruction of the object. These two functions are the topics of this chapter.

Using Constructors

The constructor is a member function that is called automatically when an object is created. Its primary job is to initialize the object to a legal initial value for the class. (It's the job of the remaining member functions to ensure that the state of the object stays legal.)

The constructor carries the same name as the class to differentiate it from the other members of the class. The designers of C++ could have made up a different rule, such as: “The constructor must be called init().” It wouldn’t have made any difference, as long as the compiler can recognize the constructor. In addition, the constructor has no return type, not even void, because it is called only automatically — if the constructor did return something, there would be no place to put it. A constructor cannot be invoked manually.

Constructing a single object

With a constructor, the class Student appears as follows:

// Constructor - example that invokes a constructor
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
public:
Student()
{
cout << "constructing student" << endl;
semesterHours = 0;
gpa = 0.0;
}
// ...other public members...
protected:
int semesterHours;
double gpa;
};

int main(int nNumberofArgs, char* pszArgs[])
{
cout << "Creating a new Student object" << endl;
Student s;

cout << "Creating a new object off the heap" << endl;
Student* pS = new Student;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

At the point of the declaration of s, the compiler inserts a call to the constructor Student::Student(). Allocating a new Student object from the heap has the same effect, as demonstrated by the output from the program:

Creating a new Student object
constructing student
Creating a new object off the heap
constructing student
Press Enter to continue...

This simple constructor was written as an inline member function. Constructors can be written also as outline functions, as shown here:

class Student
{
public:
Student();
// ...other public members...
protected:
int semesterHours;
double gpa;
};
Student::Student()
{
cout << "constructing student" << endl;
semesterHours = 0;
gpa = 0.0;
}

Constructing multiple objects

Each element of an array must be constructed on its own. For example, the following ConstructArray program creates five Student objects by declaring a single five-element array:

// ConstructArray - example that invokes a constructor
// on an array of objects
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
public:
Student()
{
cout << "constructing student" << endl;
}
};

int main(int nNumberofArgs, char* pszArgs[])
{
cout << "Creating an array of 5 Student objects"
<< endl;
Student s[5];

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Executing the program generates the following output:

Creating an array of 5 Student objects
constructing student
constructing student
constructing student
constructing student
constructing student
Press Enter to continue...

Constructing a duplex

If a class contains a data member that is an object of another class, the constructor for that class is called automatically as well. Consider the following ConstructMembers example program. I added output statements so that you can see the order in which the objects are invoked.

// ConstructMembers - the member objects of a class
// are each constructed before the
// container class constructor gets
// a shot at it
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Course
{
public:
Course(){ cout << "constructing course" << endl;}
};

class Student
{
public:
Student()
{
cout << "constructing student" << endl;
semesterHours = 0;
gpa = 0.0;
}
protected:
int semesterHours;
double gpa;
};
class Teacher
{
public:
Teacher(){cout << "constructing teacher" << endl;}
protected:
Course c;
};
class TutorPair
{
public:
TutorPair()
{
cout << "constructing tutorpair" << endl;
noMeetings = 0;
}
protected:
Student student;
Teacher teacher;
int noMeetings;
};

int main(int nNumberofArgs, char* pszArgs[])
{
cout << "Creating TutorPair object" << endl;
TutorPair tp;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Executing this program generates the following output:

Creating TutorPair object
constructing student
constructing course
constructing teacher
constructing tutorpair
Press Enter to continue...

Creating the object tp in main automatically invokes the constructor for TutorPair. Before control passes into the body of the TutorPair constructor, however, the constructors for the two-member objects, student and teacher, are invoked.

The constructor for Student is called first because it is declared first. Then the constructor for Teacher is called.

The member Teacher.c of class Course is constructed as part of building the Teacher object. The Course constructor gets a shot first. Each object within a class must construct itself before the class constructor can be invoked. Otherwise, the main constructor would not know the state of its data members.

After all member data objects have been constructed, control returns to the open brace, and the constructor for TutorPair is allowed to construct the remainder of the object.

Dissecting a Destructor

Just as objects are created, so are they destroyed (ashes to ashes, dust to dust). If a class can have a constructor to set things up, it should also have a special member function to take the object apart. This member is called the destructor.

Why you need the destructor

A class may allocate resources in the constructor; these resources need to be deallocated before the object ceases to exist. For example, if the constructor opens a file, the file needs to be closed before leaving that class or the program. Or, if the constructor allocates memory from the heap, this memory must be freed before the object goes away. The destructor allows the class to do these cleanup tasks automatically without relying on the application to call the proper member functions.

Working with destructors

The destructor member has the same name as the class but with a tilde (~) added at the front. (C++ is being cute again — the tilde is the symbol for the logical NOT operator. Get it? A destructor is a “not constructor.” Très clever.) Like a constructor, the destructor has no return type. For example, the class Student with a destructor added appears as follows:

class Student
{
public:
Student()
{
semesterHours = 0;
gpa = 0.0;
}
~Student()
{
// ...whatever assets are returned here...
}
protected:
int semesterHours;
double gpa;
};

The destructor is invoked automatically when an object is destroyed, or in C++ parlance, when an object is destructed. That sounds sort of circular (“the destructor is invoked when an object is destructed”), so I’ve avoided the term until now. For non-heap memory, you can also say, “when the object goes out of scope.” A local object goes out of scope when the function returns. A global or static object goes out of scope when the program terminates.

But what about heap memory? An object that has been allocated off the heap is destructed when it's returned to the heap using the delete command. This is demonstrated in the following DestructMembers program:

// DestructMembers - this program both constructs and
// destructs a set of data members
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Course
{
public:
Course() { cout << "constructing course" << endl; }
~Course() { cout << "destructing course" << endl; }
};

class Student
{
public:
Student() { cout << "constructing student" << endl;}
~Student() { cout << "destructing student" << endl; }
};
class Teacher
{
public:
Teacher()
{
cout << "constructing teacher" << endl;
pC = new Course;
}
~Teacher()
{
cout << "destructing teacher" << endl;
delete pC;
}
protected:
Course* pC;
};
class TutorPair
{
public:
TutorPair(){cout << "constructing tutorpair" << endl;}
~TutorPair(){cout << "destructing tutorpair" << endl; }
protected:
Student student;
Teacher teacher;
};

TutorPair* fn()
{
cout << "Creating TutorPair object in function fn()"
<< endl;
TutorPair tp;

cout << "Allocating TutorPair off the heap" << endl;
TutorPair* pTP = new TutorPair;

cout << "Returning from fn()" << endl;
return pTP;
}


int main(int nNumberofArgs, char* pszArgs[])
{
// call function fn() and then return the
// TutorPair object returned to the heap
TutorPair* pTPReturned = fn();
cout << "Return heap object to the heap" << endl;
delete pTPReturned;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The function main() invokes a function fn() that defines the object tp — this is to allow you to watch the variable go out of scope when control exits the function. fn() also allocates heap memory that it returns to main() where the memory is returned to the heap.

If you execute this program, it generates the following output:

Creating TutorPair object in function fn()
constructing student
constructing teacher
constructing course
constructing tutorpair
Allocating TutorPair off the heap
constructing student
constructing teacher
constructing course
constructing tutorpair
Returning from fn()
destructing tutorpair
destructing teacher
destructing course
destructing student
Return heap object to the heap
destructing tutorpair
destructing teacher
destructing course
destructing student
Press Enter to continue...

Each constructor is called in turn as the TutorPair object is built up, starting from the smallest data member and working up to the TutorPair::TutorPair() constructor function.

Two TutorPair objects are created. The first, tp, is defined locally to the function fn(); the second, pTP, is allocated off the heap. tp goes out of scope and is destructed when control passes out of the function. The heap memory whose address is returned from fn() is not destructed untilmain() deletes it.

image When an object is destructed, the sequence of destructors is invoked in the reverse order in which the constructors were called.

image C++ provides a separate keyword for deleting arrays, delete[]:

Student* pS = new Student[5]; // construct 5 Students

// ...later in the program...
delete[] pS; // delete heap memory and invoke
// destructor on each object

Only the delete[] keyword knows to invoke the destructor for each object allocated.