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 yAssume 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; ...
=
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
=
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
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 }
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 1and 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 &); };
Bag<Student> bagofstudents;
Bag<float> *bagoffloats;
Test all of your member functions.