Pointers

Certainly you will want to set your pointers to NULL when deleted (or when declared if they are not initialized) and you'll want to test your pointers to ensure that they are not null before using them.

SomeClass * pPointer = NULL;    // initialize to NULL if not otherwise 
                     //                         initialized

pPointer = SomeMethod();          // now assign the pointer

if ( pPointer != NULL)         // test before using!
   pPointer->SomeMemberFunction();

// … continue using the pointer

delete pPointer;              // delete the pointer
pPointer = NULL;             // reset to NULL after deleting!

Setting the pointer to NULL (or 0) has two advantages. First, it allows you to test your pointer before using it. If the test fails (if pPointer is NULL), then you can then take appropriate action (e.g. throw an exception, skip a block of code, etc.) More important, deleting a pointer (or more accurately, the object pointed to by the pointer) which has already been deleted is not legal in C++. Deleting a NULL pointer, however, is perfectly safe. By setting the pointer to NULL after calling delete, you protect yourself from crashing should you subsequently delete the same (already deleted) pointer.

Pointers are quite tricky, even for experienced programmers. It is quite easy to lose the pointer and to create a memory leak, or to find yourself with a stray pointer which no longer points to valid memory. Copying objects with pointers is even trickier, and it is critical to be careful about ensuring that objects are properly allocated and deleted when you are done with them.

A key element in using pointers well is to ensure that you provide the four canonical C++ methods:

Do not use the default methods provided by the compiler. You must write your own so that you can be certain to manage memory appropriately.

When you pass an object by value, a temporary copy of that object is made by the compiler. You pass by value when you pass a parameter that is not a pointer or a reference, or when you return an object from a method.

If the object you pass is of a user-defined class, the class's copy constructor is called by the compiler. All copy constructors take a single parameter: a reference to a constant object of the same type. For example:

   Employee::Employee(const Employee & rhs);

The convention is to refer to the parameter as rhs, which stands for right hand side. This convention began because of the assignment operator, discussed shortly. When you write,

   myEmployee  = yourEmployee;

the compiler translates this code into:

   myEmployee.operator=(yourEmployee);

Thus, the parameter passed in is the variable on the right hand side of the assignment operator (=).

In any case, if you do not create a copy constructor, the compiler will provide one for you, but that compiler-provided copy constructor will perform a member-wise (or shallow) copy of the object. This means that each member variable of the first object will be copied into the member variables of the second object.

A shallow or member-wise copy copies the exact values of one object's member variables into another object.

If these member variables are just objects, this will work fairly well, but if they are pointers, then disaster ensues. Let's look at an example to see why. Suppose you have an Employee class that looks like this:

class Employee
{
public:
   Employee(); 
   ~Employee();
   // note no copy constructor
   // using the compiler-supplied shallow copy constructor

   // other methods here...

private:
   int myAge;                 // how old is the employee
   EmployeeLevel* pEmpLevel;       // ptr to EmployeeLevel object
};

For the moment, we don't care what an EmployeeLevel object looks like, but we note that every Employee object has a pointer to one. If we then pass an Employee object into a method by value, like this:

   Employee fred;
   SomeMethodWhichTakesAnEmployeeByValue(fred);

Void SomeMethodWhichTakesAnEmployeeByValue(Employee theEmployee)
{
   // does some work with theEmployee
}

Then the compiler must copy fred to a temporary object, named theEmployee, inside the method SomeMethodWhichTakesAnEmployeeByValue(). Because we did not provide a copy constructor, the compiler-supplied shallow copy constructor will take over. It will copy the value in fred.myAge into theEmployee.myAge and it will copy the value inside fred.pEmpLevel into theEmployee.pEmpLevel.

What is wrong with that? The value in fred.pEmpLevel is a memory address — the address of the EmployeeLevel object which was presumably created on the heap when fred was initialized. Unfortunately, now two objects point to that same memory:

When the function returns, the temporary object (theEmployee) will be destroyed. The Employee destructor will be called, which ought to delete the pointer. Unfortunately, the pointer points to the same object in memory as that pointed to by fred, leaving fred with a pointer to what is now a destroyed EmployeeLevel object. The result is that when you use fred's pointer again, the system will crash.

The alternative is to write your own copy constructor and to allocate memory for the new pointer. This creates a deep copy — one which copies not only the absolute value of each member variable, but which copies those objects that are pointed to as well.

A deep copy copies the values allocated on the heap to newly allocated memory.

Employee::Employee( const Employee & rhs):
myAge(rhs.myAge),
pEmpLevel(new EmployeeLevel(rhs.pEmpLevel))
{
}

This copy constructor now initializes the member variables appropriately, allocating memory for the object pointed to by pEmpLevel. If you are not familiar with object initialization, take a moment to look up this technique. It is far more efficient to initialize these objects than to assign them in the body of the constructor:

Employee::Employee( const Employee & rhs)
{
   myAge = rhs.myAge;
   pEmpLevel = new EmployeeLevel(rhs.pEmpLevel);
}

While this works, it creates unnecessary temporary copies, and thus is slower and uses more memory. In addition, if any of the member variables are constant or are references you must initialize and cannot use assignment.

© 1998 by Wrox Press. All rights reserved.