Monday, October 16, 2017

Lecture Notes 06

Object Oriented Programming: Lecture Notes 06

Author: A. El-Gadi/Faculty of Computer Engineering/Tripoli University

Destructors


We have seen that constructors are used to create objects. When execution exits the scope of the object, the object is destroyed. The destruction part is done by the destructor, which can be overridden to control how an object is destroyed.

class A{public: int k;};

int main(){A a1;
   
    return 0;/*Destructor is called here as scope exits when main returns*/
    }


As with constructors, every class is supplied with a default destructor. If not overridden the default constructor will simply release the memory occupied by the object. It should be noted that, in contrast to constructors, destructors cannot be overloaded, which means that there is always one destructor that takes no arguments. The code below illustrate the syntax of overriding the default destructor:

class A{public: int k;
    ~A(){cout<<"Object is destroyed\n";}
    };

int main(){A a1;
   
    return 0;/*Output: Object is destroyed*/
    }


It should be clear that destructors are the most unlikely place to communicate with the user through the console, and is done here for illustration purpose only. A good and typical use of destructors will be mentioned below when we consider deep objects. To understand what a deep object is, we need to make understand how pointers work in C++.

Pointers and the new and delete operators


As every C programmer knows, pointers are memory addresses that refer to the location of a variable. In addition to C-like pointers, pointers to objects also exist. When declaring a pointer to a basic type (float, int, char ..etc.) the referent of the pointer is created along with the pointer. However, when a pointer to an object is declared the referent of the pointer needs to be created independently. The keyword new is reserved for this purpose in C++. It creates an object and returns a pointer to the object. To access member variables and member functions of the object the arrow operator -> is used in the case of pointers. Below is an example of the use of the new operator.

class A{public: double x,y;
        double mul(){return x*y;}
    };

int main(){A *pa; /*Declares a pointer to an object of type A. The object does not exist yet.*/
      
       pa=new A(); /*An object of type a is created and its address is returned.*/
       pa->x=3.2;
       pa->y=0.2;
       cout<<pa->mul();//Output: 0.64
       /*There is something missing here*/
       return 0;
    }


This code, however, will cause compilation error because we are attempting to access a member of a non-existent object:

class A{public: double x,y;
        double mul(){return x*y;}
    };

int main(){A *pa; /*Declares a pointer to an object of type A. The object does not exist yet.*/

       pa->x=18; /*Compilation error. The object is not existent yet.*/
       return 0;
    }


Objects created using the new operator are said to be dynamically allocated because they are allocated at the run-time. For this reason, they should be deallocated when no longer needed. This is accomplished using the delete operator and is normally placed at the end of the scope where the object was created. Failing to delete dynamically allocated objects results in memory leaks, with all their concomitant problems. It is important to realize that C++ does not implement automatic garbage collection as in other languages (e.g. Java); because automatic garbage collection leads to a less efficient code. C++ also allows for more control at the side of the programmer. The code above is, therefore, produces a memory leak because the object was not deleted. Here is the code after fixing it:

class A{public: double x,y;
        A(double u, double w){x=u;y=w;}
        double mul(){return x*y;}
    };

int main(){A *pa; /*Declares a pointer to an object of type A. The object does not exist yet.*/
      
       pa=new A(); /*An object of type a is created and its address is returned.*/
       pa->x=3.2;
       pa->y=0.2;
       cout<<pa->mul();//Output: 0.64
       delete pa;/*Object deleted*/
       return 0;
    }


What the delete operator actually does, is invoke the class destructor. Take for example the following code:

class A{public: double x;
        ~A(){cout<<"Bye Bye";}
    };

int main(){A *pa; /*Declares a pointer to an object of type A. The object does not exist yet.*/
      
       pa=new A(); /*An object of type a is created and its address is returned.*/

       delete pa;/*Output: Bye Bye*/
       return 0;
    }


This brings us to one of the typical usages of destructors; namely deep classes and their objects.

Deep classes/objects


A deep class is a class that has one or more pointers member variables. The presence of such member variables mandates extra care when designing the class. First, the objects to which the pointers should point need to be created dynamically, usually, somewhere in the class. Those dynamically created objects, need to be destroyed when the object whose member pointers point to them is destroyed. Otherwise, they will stay hanging in memory and cause a memory leak. Below is an example of a deep class, together with the proper design of the destructor.

class AA{public: int d,e;};

class BB{public: int f,g;
    AA *a;
    BB(){a=new AA();}
    ~BB(){delete a;}
};


Indeed, a class can be of any depth. If the dynamic creation and deletion of objects is taken care of, the design remains the same.


class AA{public: int d,e;};

class BB{public: int f,g;
    AA *a;
    BB(){a=new AA();}
    ~BB(){delete a;}
};

class CC{public: int h,i;
    BB *b;
    CC(){b=new BB();}
    ~CC(){delete b;}
    };


There are more considerations, to pay attention to, when designing deep classes, but we will leave them for the time being.

It is important, to mention in that member arrays do not lead to deep objects. Therefore, the class below is a shallow class.

class AA{public: int d,e;

          int ar[4];};/*This array is inside the bounds of the class*/

And class DD is shallow as well.

class AA{public: int d,e;};

class DD{public:
        AA aaar[4];
    };






3 comments:

  1. Take note everybody! The last part of the post-the one about arrays- has been corrected.

    ReplyDelete
  2. "pa=new A(); /*An object of type a is created and its address is returned.*/"

    when dynamically allocating an object of any class using New is it necessary to add constructor parentheses?

    ReplyDelete
    Replies
    1. No it's not. There is a slight difference between the two versions, however, that need not concern us in this course.

      Delete