Pointers and Dynamic Objects In C++ (and a bit of Java, too)


Pointer Fundamentals

Fundamentally, pointers 
are used to achieve two different ends: 
dynamic data structures and indirection.

Dynamic data structures are those data structures whose size and shape
can change at run-time.  Linked-lists and binary trees are two common
examples. The former covered in CS2 and the latter in the Data Structures course. 
Pointers enable the existence of such dynamic structures
but giving the programer the ability to reference unnamed variables.
Pointers
enable a flexible mechanism to attach multiple data objects to a single
named variable at run-time.  For example, in a linked-list of Nodes, a
single Node pointer named "head" may be used to access any number of
data objects at run-time (e.g. head->next->next->data).

Indirection is a general term for the aliasing capabilities of pointers.
Two names in a program can refer to the same object thanks to pointer
indirection.  


Problems and pitfalls of using   pointers

Problems associated with pointers include a
number of correctness or efficiency costs, which include 

 - unintended aliasing
 - memory leaks 
 - dangling references (in languages like C++) 
 - costly garbage collection (in languages like Java)
 - common logic errors
 - cloning problems 



Stack example

Here is the code for the Stack class, as well as the ListNode class
which is used to implement the linked-list in the "guts" of the Stack.

//Stack Definition

   class ListNode{
   public:
      int data;            // these are the instance variables
      ListNode *next;
   }

   class Stack{
   private:
      ListNode *_Top;      // only instance variable

   public:
    Stack();   //CONSTRUCTOR
    ~Stack();  //DESTRUCTOR
    bool TestEmpty();  //CHECKS FOR EMPTY STACK
    void Push(char Value);  //INSERT (PUSH) ELEMENT ON TOP OF STACK
    char Pop();  //REMOVE (POP) ELEMENT FROM TOP OF STACK
   }


// Stack Implementation
Stack::Stack()
{
   _Top = 0;
}//END CONSTRUCTOR Stack()

//DESTRUCTOR
Stack::~Stack()
{
   
   while(! TestEmpty())
   {
       Pop();
   }
}//END DESTRUCTOR ~Stack()

//STACK OPERATIONS
bool Stack::TestEmpty()
{
  return(_Top == 0);

}//END EmptyStack()

void Stack::Push(char Value)
{
   ListNode *PtrNewListNode;
   PtrNewListNode = new ListNode;
   PtrNewListNode ->Info = Value;
   PtrNewListNode ->Next = _Top;
   _Top = PtrNewListNode;
}//END Push()

int Stack::Pop()
{
   int Value;
   ListNode *Temp;
   if(! TestEmpty())
   {
       Value = _Top -> Info;
       Temp = _Top;
       _Top = _Top -> Next;
       delete Temp;
       return Value;
   }
   else
      cout << "\nSTACK UNDERFLOW!";
}//END Pop()


The first problem is memory management and deallocation.  
In C++ we have to
explicitly deallocate Nodes  when we pop() them off the stack: It is
easy to forget to do this, causing a memory leak.  We must also
provide a destructor to deallocate the list when a stack object gets
deleted ~Stack(). In general manual allocation and
deallocation is tedious and error-prone.

The second problem comes from making copies of Stacks.  Copy
constructors and assignment operators are a cause of concern with
dynamic objects. C++ provides a default copy constructor, which does a
member-wise copy of the instance variables.  Since "_Top" is a
pointer, though, this default behavior means that a copy of a Stack
will share the same ListNodes as the original.  This is gnerally not
what a programmer intends by copying.
We must remember to create our own
copy-constructor, which overrides the default behavior and actually
copies the ListNodes, so each Stack has its own list to work with.
The topic of Copy constructors is dealt with in other courses.


To see a related JAVA Implementation of a dynamic stack
 click here.