Lecture 26


In this lecture...

  • Using Destructors.
  • Queues.
  • Copy Constructor.
  • Assignment Operator.
  • chapter 2.2.4 in the textbook.


    Using Destructors.

    A destructor is a special member of a class. A class destructor is called automatically. The destructor name is the tilde (~) character followed by the class name.

    A destructor has NO parameters and NO return value (even void cannot be specified). There is only one destructor in a class.

    class Demo {
    public:
          Demo( int x ) : data(x) {cout<<"object "<x<" constructed";}  
         ~Demo()    {cout<<"object "<<data<<" destructed"<<endl;}    
    private:
    	int data;
    };
    

    Constructors and destructors are called each time when you allocate and/or deallocate memory by using new and delete:

    int main( void )
    {
    	Demo *tmp = new Demo(10);
    	cout << endl;
    	delete tmp; //comment out this line and see the difference
    	
    	return 0;
    }
    

    Exercise. How would you call the destructor directly (without calling delete)?

    Destructors for automatic objects are called each time when the object enters or leaves the scope. Destructors for static objects are called when main() terminates. Here is the program which demonstrates the order of calling constructor and destructor:

    int main( void )
    {
    	Demo one( 1 );
    	cout << "   (local automatic in main)" << endl;
    
    	static Demo two( 2 );
    	cout << "   (local static in main)" << endl;
    
    	Demo three( 3 );
    	cout << "   (local automatic in main)" << endl;
    
    	return 0;
    }
    

    Let us add the function create() which creates a few more objects. Here is a complete code.


    Queues.

    Another fundamental restricted-access data structure is called the queue. A typical example of the queue is a line of customers waiting at a checkout counter. In this way, the customer that has been waiting longer is served first. A queue called FIFO: first in, first out (FIFO). Again, only two basic operations are involved: insert an item into the queue at the end and remove an item from the other end. In a stack these operations are called push and pop, but in a queue they are called enqueue and dequeue. Enqueue (push) means to insert an item into the end/tail of the queue, dequeue (pop) means remove the element from the front/head:

    Queues have many applications in computer systems: printer queue, system queue, information packets in the network, file access

    As with stacks, we can implement a queue using either linked lists or arrays. In both cases we need to keep track the beginning and the end of a queue, since it grows and shrinks as elements are inserted and deleted.

    Linked List Implementation.
    The homemade picture of the queue. We build class Queue by using the structure Node. We want the queue to have member functions Dequeue, Enqueue, Print, IsEmpty, RemoveAll
    class Queue
    {
     public:
        // default constructor
        Queue () : HeadOfQueue(NULL),BackOfQueue(NULL) {}
        // copy constructor
        
        // destructor
        
        // accessor
        bool IsEmpty() const;
        void Print() const;  // print a queue
        
        // member functions
        void Enqueue(const int &); // add item to the back
        bool Dequeue(int &);  // return and remove least recent item       
        void RemoveAll();       
        
     private:
        Node* HeadOfQueue;
        Node* BackOfQueue;
    };
    
    Download the zip file and let us implement a few member functions.

    Copy Constructor.

    Declaration

    Stack(const Stack &);
    
    A copy constructor is supposed to construct a new object S2 which is initialized to a copy of S. The copy constructor performs a memberwise copy - each member of one object is copied individually to the same member in another class.
    Stack S2 = S;
    A copy constructor is called in the following cases
  • when the object is passed by value
  • when the object is returned by value
  • declaration with initialization, (but not S2 = S )
  • Implementation (for stack)

    Stack::Stack(const Stack &rhs)
    {
    	HeadOfStack = NULL; // initialize lhs
    	*this = rhs;        // point lhs to rhs object
    }
    

    HeadOfStack and this point to the left hand side class.

    Exercise. Implement a copy constructor for the queue.

    You should understand the difference between initialization and assignment. Initialization uses the copy constructor to create a new object, the assignment operator S2 = S deals with an existent object S2 and modifies it so that it is an identical copy of S.

    The problems occurs when when the member of a class is a pointer. The copy constructor will copy the value of the pointer, which lead to having two pointers to the same object. This is so-called shallow copy. To avoid this problem you have to implement your own assignment operator, a deep copy.


    Assignment Operator.

    Typically, when a class contains pointers as data members we must implement the destructor, copy constructor and operator=.

    Declaration

    const Queue & operator=(const Queue &);
    

    When you do assignment S2 = S you should make sure that

    - we don't assign S2 to itself this != &rhs
    - the object S2 is empty
    - all elements are correctly copied
    Here is an implementation
    const Queue& Queue::operator=(const Queue &rhs)
    {
       if (this != &rhs)  
       {
    	  RemoveAll();   
    	  if (rhs.IsEmpty()) return *this;
    
    //we copy the first node
    //tmp points to rhs (right hand side)
    //new_tmp points to lhs (left hand side)
    
    	  HeadOfQueue = new Node(rhs.HeadOfQueue->data);
    	  Node* tmp = rhs.HeadOfQueue->next;
    	  Node* new_tmp = HeadOfQueue;
    
    //in a loop we cope all other nodes
    	  while (tmp)  	
    	  {
    	     new_tmp = new_tmp->next = new Node(tmp->data);
    	     tmp = tmp->next;
    	  }
       	  BackOfQueue = new_tmp;
       }
       return *this;  //why do we return a pointer this?
    
    HeadOfStack, new_tmp and this point to a new stack.
    The picture helps to understand.

    What should the assignment operator return? Think about the following infinite loop

    while(x = 7);
    
    The result of assignment is 7. The operator= usually returns a constant reference.

    Last updated February 01, 2001.
    Please send corrections to Victor S. Adamchik
    Computer Science Department, 
    Carnegie Mellon University, Pittsburgh, PA.