Class Constructor and Destructor

Dr. Lawlor, CS 202, CS, UAF

OK, so you've got classes--code and data together.  The typical problem is that the data needs to be:
  1. Set up
  2. Used
  3. Cleaned up
And, you've got to do them in that exact order.  For example, the "company" class from the last lecture really needs to initialize its totals to zero at startup, and really needs to send a check off to the IRS afterwards. 

Constructor and Destructor Syntax

So C++ classes can start with a "constructor", which is like a method that runs to set up the class.  The constructor is named the same thing as the class; so for a class named "Sammy", the constructor is also named "Sammy", and it runs whenever you allocate a Sammy object:
class Sammy {
int value; // <- keeps a running total
public:
Sammy(void) { // constructor
value=0;
cout<<" Hi, I'm Sammy!\n";
}
};
int foo(void) {
cout<<"About to make a class...\n";
Sammy s;
return 0;
}

(Try this in NetRun now!)

The constructor performs step 1, data set up.  Object methods use the data, step 2.  To clean up the data, you need some code to run when the class is finished.  C++ supports a "destructor", which is called when the object is deleted.  Destructors are named like constructors, but with a tilde, a "~", in front of the name.

Here's an example with both a constructor and a destructor.  Note that the constructor is called when the class is created, and the destructor is called when the class is destroyed at the end of the function.
class Sammy {
int value;
public:
Sammy(void) { // constructor
value=0;
cout<<" Hi, I'm Sammy!\n";
}
void add(int v) {
value+=v;
cout<<" My total is now "<<value<<"!\n";
}
~Sammy() { // destructor
cout<<" Bye!\n";
}
};

int foo(void) {
cout<<"About to make a class...\n";
Sammy s;
cout<<"Calling class's add method...\n";
s.add(14);
cout<<"Returning from function...\n";
return 0;
}

(Try this in NetRun now!)

Constructors with Arguments

If you need to pass some data into a class to set it up, you can pass parameters to the constructor:
class Sammy {
int value;
public:
Sammy(int v) { // constructor, taking initial value
value=v;
cout<<" Hi, I'm Sammy!\n";
}
void add(int v) {
value+=v;
cout<<" My total is now "<<value<<"!\n";
}
~Sammy() { // destructor
cout<<" Bye!\n";
}
};

int foo(void) {
cout<<"About to make a class...\n";
Sammy s(100); // pass 100 to Sammy's constructor
cout<<"Calling class's add method...\n";
s.add(14);
cout<<"Returning from function...\n";
return 0;
}

(Try this in NetRun now!)

You can even have several different constructors taking different arguments, or no arguments (the "default constructor").  For example, my standard "vec3" class has a whole set of constructors:
class vec3 {
public:
    float x,y,z; // components of the vector
    vec3(float x_,float y_,float z_) { x=x_; y=y_; z=z_; } // call like: vec3 A(3,4,5);
    vec3(float v) {x=y=z=v; }  // call like: vec3 B(0.0);
    vec3(float *arr) {x=arr[0]; y=arr[1]; z=arr[2]; }  // call like: vec3 C(&floatarr[9]);
    vec3(void) {x=y=z=0.0; } // default constructor: vec3 D;
    ...
};
Destructors can't take arguments, so there's only one destructor.

Sentinals

Because destructors always run when your function exits, sometimes people make classes that do work only inside the destructor.  A class like this is called a "sentinal", since it waits silently until it is called upon for some task.

For example, if you keep forgetting to flush the log file (and so getting an empty log file), you could make an "AutoFlush" class that will flush the file when your code returns:

class AutoFlush {
fstream *file; // to flush
public:
AutoFlush(fstream *f) {file=f;}
~AutoFlush() {file->flush();} // flush in destructor
};

fstream outfile("log.txt",ios::out);

void doWork(void) {
AutoFlush flusher(&outfile); // <- guarantees flush before return
for (int i=0;i<17;i++) {
outfile<<"still alive at step "<<i<<"\n";
if (i==12) { return; } // <- oops!
}
outfile.flush(); //<- you remembered to flush here...
}

int foo(void) {
doWork();
cat("log.txt");
return 0;
}

Resource Aquisition Is Initialization

One extremely common and powerful trick in C++ is to allocate memory in your constructor, and release the memory in your destructor.  This makes it much harder to forget to deallocate memory.  For example, here's a "smart array" class that checks array bounds, and automatically deletes the data when your function returns.
class SmartArray {
int length;
int *data;
public:
SmartArray(int len) {
length=len;
data=new int[length];
}
// Get this value from the array, or exit if a bad index
int &get(int index) {
if (index<0 || index>=length)
{ // index is out of bounds!
cout<<"Index "<<index<<" out of bounds 0.."<<length-1<<"!\n";
exit(1);
}
return data[index];
}
~SmartArray() {
delete[] data;
}
};

int foo(void) {
SmartArray arr(10); // <- array length is constructor parameter
for (int i=0;i<10;i++) {
arr.get(i)=1000+i;
}
return arr.get(7);
}

(Try this in NetRun now!)