Computer Science II
Spring, 1998
Worksheet 14

Pointers to Classes

It is possible to create pointers to classes, and most large programs do so. Pointers to classes are so common that there is a special operator -> to access members of pointers to classes (this is written as the minus char followed by the greater than char).

Suppose we have a class Point (a point in two dimensional space) with the usual member functions:

Point(); // constructor which sets x and y to zero
Point(float x, float y); // constructor which sets x and y
void Setx(float x); // sets or changes the value of x
void Sety(float y); // sets or changes the value of y
float Getx(); // returns the value of x
float Gety(); returns the value of y
Assume that the << operator is overloaded for the class to output the values of x and y.

Here is a short example which demonstrates the use of the -> operator.

1 #include "Point.h"
2 int main()
3 {
4    Point *ptr = new Point(3.45,6.78);
5    float f = ptr->Getx();
6    ptr->Setx(23.45);
7    cout << *ptr << endl;
8    // yada yada yada
9 }
We could also have written lines five and six as follows:
    float f = (*ptr).Getx();
    (*ptr).Setx(23.45);
but the use of the -> is customary and generally easier to read. The same is true of pointers to structs in C.

We can create an array of pointers to Points, allocate memory for them as needed, and deallocate memory when they are no longer needed.

#include "Point.h"
int main()
{
  Point *arr[20];  // an array of 20 pointers to points
  
  arr[0] = new Point(); 
  arr[0]->Setx(45.67);
  arr[0]->Sety(32.46);
  cout << *arr[0] << endl;

  arr[1] = new Point(3.14, 4.56);
  cout << *arr[1] << endl;

  // once we are finished with the point arr[1], we can
  // free up the memory with delete.

  delete arr[1];
  arr[1] = NULL;
}

An important concept in object oriented program is the lifetime of an object. In many programs which run for long periods of time, objects are created, used for a while, and then deleted. When an object is deleted, it is important to free up the memory so that memory leaks don't develop. It is often the case that there are other things that need to be done when an object is deleted. A simple way to handle whatever housekeeping needs to be done is to use a destructor function. This is a function which is automatically called when an object is destroyed. Like a constructor, it has no return type. The destructor function has the name ~ followed by the name of the class (e.g. ~Point()).

If a class has memory which is dynamically allocated by one of its member functions, this memory must be freed when the instance of the class is destroyed, and this is an important function of the destructor. For example, suppose our class Student is defined as follows:

// Student.h
class Student {
private:
   char *firstname;
   char *lastname;
   // other stuff
public:
   Student(const char *fname, const char *lname) {
        firstname = new char[strlen(fname) + 1];
        strcpy(firstname,fname);
        lastname = new char[strlen(lname) + 1];
        strcpy(lastname,lname);
   }
   // other stuff
   ~Student() {  // destructor
        delete [] firstname;
        delete [] lastname;
   }
};

// main.C
#include "Student.h"
#include <iostream.h>
int main()
{
    Student *freshmen[10];
    freshmen[0] = new Student("Mickey", "Mouse");
    // do stuff with Mickey Mouse
    delete freshmen[0];  // destructor is automatically called here
    freshmen[0]=NULL;
    return 0;
}

The size of the class Student is only the size of two pointers (pointers are typically 32 bits, but you hardly ever need to know this), so the statement
freshmen[0] = new Student("Mickey", "Mouse");
only allocates enough memory from the heap for two pointers. However, the constructor function is called, and this allocates additional memory from the heap for the first name and the last name.

When an instance of the student is no longer needed, it is not sufficient to merely delete the memory needed for the class; it is also necessary to delete the memory which had been dynamically allocated for the the first name and the last name. If this were not done, memory would be allocated to your program which your program could no longer access. This is wasteful, and in large programs which run for a long time, it would eventually cause the program to run out of memory.

The this pointer

The C++ keyword this can be used within any member function of a class. It points to the instance of the class. To access members of the class, you typically don't need to use this; it's use is implied, but you can use it if you wish. Here is a trivial example.

class simple {
private:
   int data;
public:
   simple(int x) {this->data=x;)
   int getdata() {return this->data;}
};
You typically omit the this-> in the two member functions.

There are times when you need to use this, in particular, as a return value. We will see this in the next section.

The copy constructor and overloading the assignment operator

It often happens that you want to create a new object and initialize it with the same values as another instance of the object which has already been created. This calls for a special constructor called a copy constructor.

class someclass {
...
main()
   someclass A(...)
   someclass B(A);  // use of the copy constructor
   ...

C++ automatically provides a default copy constructor which simply copies all data to the new instance of the class from old instance (i.e. from A to B). For many classes this is exactly what you want.

A very similar problem arises with the assignment operator if you wish to assign to one instance of the class the values of another instance of the class

     main() 
        ...
        someclass A(..)
        ...
        someclass B;
        B = A;
        ...

As with the copy constructor, C++ automatically overloads the assignment operator = for new classes, and the action is to copy the values of the object on the right side of the operator to those on the left side.

We have seen that there are problems when you use the = operator for strings or other pointers when you really wanted to use the strcpy function or the equivalent, and these problems arise when you use the default copy constructor and assignment operator for classes involving pointers. Here is an example.

#include <iostream.h>
class simple {
private:
  char *data;
public:
  simple() {data = NULL;}
  simple(char *s) {
     data = new char[32];
     strcpy(data,s);
  }
  char *getdata() {return data;}
  void setdata(char *s) {
      if (data == NULL) data = new char[32];
      strcpy(data,s);
  }
};

main()
{
    simple A("Mickey Mouse");
    simple B;
    B = A;
    simple C(A);
    cout << A.getdata() << endl;
    cout << B.getdata() << endl;
    cout << C.getdata() << endl;
    A.setdata("Donald Duck");
    cout << A.getdata() << endl;
    cout << B.getdata() << endl;
    cout << C.getdata() << endl;
}

Study this code closely. It creates three instances of the class simple, called A, B, and C. A is initialized using a constructor, B is set using the assignment operator, and C is initialized using the copy constructor. Note that there is no code to overload the assignment operator (=) and there is no code for a copy constructor. In both cases the defaults are used. The problem is that this code does not behave properly. The value of data is changed from ``Mickey Mouse'' to ``Donald Duck'' in A, but this has the unwanted effect of changing the value in all three. Here is the output of this program

Mickey Mouse
Mickey Mouse
Mickey Mouse
Donald Duck
Donald Duck
Donald Duck

The reason why this program does not work properly is that the value of data in instances B and C is set using the = operator, which means that data, which is a pointer, is pointing to the same area of memory for all three instances. Thus, changing the contents of this memory has the effect of changing the value of data in all three instances.

C++ allows you to write your own copy constructor and to overload the assignment operator for classes if you need to in order to correct this problem. Here is the revised code to correct this problem.

#include <iostream.h>
class simple {
private:
  char *data;
public:
  simple() {data = NULL;}
  simple(char *s) {
     data = new char[32];
     strcpy(data,s);
  }
  simple(const simple &s) {  // the copy constructor
      data = new char[32];
      strcpy(data,s.data);
  }
  char *getdata() {return data;}
  void setdata(char *s) {
      if (data == NULL) data = new char[32];
      strcpy(data,s);
  }
  simple operator=(const simple &s) {  // overload assignment operator
    if (this != &s) {   // do not copy to yourself
      if (data!=NULL) delete [] data; //free up memory
       data = new char[32];
       strcpy(data,s.data);
    }
    return *this;
  }
};

main()
{
    simple A("Mickey Mouse");
    simple B;
    B = A;
    simple C(A);
    cout << A.getdata() << endl;
    cout << B.getdata() << endl;
    cout << C.getdata() << endl;
    A.setdata("Donald Duck");
    cout << A.getdata() << endl;
    cout << B.getdata() << endl;
    cout << C.getdata() << endl;
}

Here is the output of this program:

Mickey Mouse
Mickey Mouse
Mickey Mouse
Donald Duck
Mickey Mouse
Mickey Mouse

This is presumably the correct output because changing the value of data in A has no affect on the value of data in B and C.

Notice that the argument is passed in as a const reference pointer in the copy constructor and in the overloaded = operator. The keyword const means that the function is not permitted to change the values of s. However, since the default is call-by-value, you should be asking why this is passed in as a reference value. The answer is efficiency. For each function call, a copy is made of each value parameter. For simple data types, this is not a problem, but for potentially large data types such as classes, this copying can be quite time consuming. Reference pointers simply pass in a pointer to the data, which is always very efficient.

In C and C++, the = (assignment) operator returns a copy of what was assigned. Here are two examples where this return value is used.

1  int x, y, z;
2  x = y = z = 3;     
3  while (x=y) {
4      cout << x << ' ';
5       y--;
6   }

The = operator associates right to left (in contrast to the - operator which associates left to right) This means that the leftmost operation is done first and the return value is

then used as the right operand for the next operation. You can see this in line 2. The operation z = 3 is done first. This operation returns 3, so this value is then assigned to y. This operation also returns 3 and this value is assigned to x.

In line 3, the statement inside the parentheses looks like it is wrong but in fact, what it is doing is assigning the value of y to the value of x, and this operation returns that value. Thus, this is a strange way of looping until y has the value zero (remember that zero means false and nonzero means true). This code would print

3 2 1
and after the loop, both x and y would have the value zero.

This is an elaborate digression to explain why the overloaded = operator returns a value of type simple; and this in turn explains the statement
return *this;
in the code which overloads the = operator.

Exercise 1 Write code for a template class Bag which starts out as follows:

template <class T>
class Bag {
private:
   T *thedata[100];  // note that this is an array of pointers
   int size;
public:
   Bag();
   Bag(const Bag&);  // copy constructor
   int BagSize();    // returns the number of elements in the bag
   void Insert(T *); // inserts a new element into the bag
   void PrintBag();  // prints each element in the bag
   ~Bag();           // a destructor
   friend Bag operator=(Bag &, const Bag &);
};

Write a main which creates a bag of students and a pointer to a bag of floats. i.e.
Bag<Student> bagofstudents;
Bag<float> *bagoffloats;

Test all of your member functions.