15.6 DESTRUCTORS FOR DERIVED CLASSES IN C


15.6 DESTRUCTORS FOR DERIVED CLASSES IN C++

A destructor defined in a base class cannot be inherited by a derived class. However, the base class destructor can be overridden in a derived class. The usefulness of overriding a base class destructor in a derived class is discussed separately in Section 15.10.

If resources are appropriated in a derived class in C++, then a destructor must be explicitly defined for the derived class in order to free up those resources when an object made from the derived class goes out of scope or is explicitly deleted from the heap.

To illustrate the importance of defining a destructor explicitly for a derived class, let's consider a class X and a class Y derived from X, as shown in Figure 15.4. We will assume that the baseXpossesses a pointer data member that is meant to point to a block of freshly allocated memory in each object of type X. We will also assume thatXis provided with a destructor to free up this memory. With regard to the derived Y, we will consider the following three cases:


Figure 15.4

  • The derived classYdoes not appropriate any resources and is not provided with a destructor.

  • The derived classYalso has a pointer data member that is meant to point to a block of freshly allocated memory in each object of type Y, but is not provided with a destructor.

  • The derived classYhas a pointer data member as mentioned above and is also provided with an appropriate destructor.

Case 1

This case can be depicted as in Figure 15.5.

click to expand
Figure 15.5

The following program is an implementation of this case. The derived classYdoes not appropriate any resources and is not provided with a destructor. Therefore, for class Y, we are dependent on the system-supplied default destructor, which suffices.

When the system tries to destroy a derived-class object (either because it is going out of scope or because the delete operator was invoked on a pointer to the object) as in line (F), the default definition of the destructor works fine. The default destructor forYinvokes the user-supplied destructor in line (B) for the parent classXto free up the memory allocated in theXsubobject of theYobject.

 
//DerivedDestructCase1.cc #include <iostream> using namespace std; class X { // BASE public: int* x_data; int x_size; //constructor: X(int* ptr, int sz) : x_size(sz) { cout << "X's constructor invoked" << end1; //(A) x_data = new int[ x_size ]; int i=0; int* temp = x_data; while (i++<x_size) *temp++ = *ptr++; } //destructor: ~X() { //(B) cout << "X's destructor invoked" << end1; //(C) delete [] x_data; } }; //class Y is NOT supplied with a programmer-defined destructor: class Y : public X { // DERIVED int y; public: Y(int* xptr, int xsz, int yy) : X(xptr, xsz), y(yy) { cout << "Y's constructor invoked" << end1; //(D) } }; int main() { int freshData[100] = {0}; Y* yptr = new Y(freshData, 100, 1000); //(E) delete yptr; //(F) return 0; }

We first construct a Y object in line (E). Then, in line (F) the delete operator invokes the system-supplied default destructor for the Y object. This default destructor invokes the user-supplied X's destructor of line (B) for that portion of the Y object that is X. So the output of this program, as produced by lines (A), (C), and (D), is

      X's constructor invoked      Y's constructor invoked      X's destructor invoked 

This output also demonstrates that an object of a derived-class type is constructed base up, meaning that first its base subobject is constructed and then the rest of the derived-class object is created.

Case 2

Let's now consider the case when objects of derived-class type also acquire freshly allocated memory on the heap, as depicted in Figure 15.6. Now we must provide a destructor for Y also. The job of Y's destructor would be to free up the memory appropriated during the construction of a Y object as the Y object goes out of scope (or as the delete operator is applied to a pointer of type Y*). But just to see what happens, let's consider the following program, deliberately flawed, that does not provide a destructor for Y:

click to expand
Figure 15.6

 
//DerivedDestructCase2.cc #include <iostream> using namespace std; class X { // BASE public: int* x_data; int x_size; //constructor: X(int* ptr, int sz) : x_size(sz) { cout << "X's constructor invoked" << end1; x_data = new int[ x_size ]; int i=0; int* temp = x_data; while (i++<x_size) *temp++ = *ptr++; } //destructor: ~X() { //(G) cout << "X's destructor invoked" << end1; delete [] x_data; } }; //class Y has NOT been supplied with a //programmer-defined destructor even //though it needs one: class Y : public X { // DERIVED int* y_data; int y_size; public: //constructor: Y(int* xptr, int xsz, int* yptr, int yz) : X(xptr, xsz), y_size(yz) { cout << "Y's constructor invoked" << end1; y_data = new int[ y_size ]; //(H) int i=0; int* temp = y_data; while (i++<x_size) *temp++ = *yptr++; } }; int main() { int freshData[100] = {0}; int moreFreshData[ 1000 ] = {1}; Y* yptr = new Y(freshData, 100, moreFreshData, 1000); //(I) delete yptr; //(J) return 0; }

Note that we have not defined a destructor for the derived class Y even though we are appropriating memory for its pointer data member in line (H). We again construct a Y object in line (I) and then call for its destruction in line (J) of main ().

Since Y is not provided with a destructor, the statement in line (J) invokes the system-supplied default destructor for Y, which, frees up the eight bytes of memory occupied by the two data members of the Y object and invokes the base's destructor in line (G) for destroying the X subobject inside the Y object. As a result, the memory that was appropriated specially for the Y object through line (H) is never deallocated.

Therefore, this program has a memory leak with respect to the memory to which the pointer y_data points. The program produces the following output:

      X's constructor invoked      Y's constructor invoked      X's destructor invoked 

which is the same as for Case 1.

Case 3

This case is the same as the previous one, except that now we show a correct program, in the sense that the derived class is now provided with a destructor of its own.

 
//DerivedDestructCase3.cc #include <iostream> using namespace std; class X { // BASE public: int* x_data; int x_size; //constructor: X(int* ptr, int sz) : x_size(sz) { cout << "X's constructor invoked" << end1; x_data = new int [ x_size ]; int i=0; int* temp = x_data; while (i++<x_size) *temp++ = *ptr++; } //destructor: ~X() { cout << "X's destructor invoked" << end1; delete [] x_data; } }; class Y : public X { // DERIVED int* y_data; int y_size; public: //constructor: Y(int* xptr, int xsz, int* yptr, int yz) : X(xptr, xsz), y_size(yz) { cout << "Y's constructor invoked" << end1; y_data = new int [ y_size ]; int i=0; int* temp = y_data; while (i++<x_size) *temp++ = *yptr++; } //destructor: ~Y() { cout << "Y's destructor invoked" << end1; delete [] y_data; } }; int main() { int freshData[100] = {0}; int moreFreshData[ 1000 ] = {1}; Y* yptr = new Y(freshData, 100, moreFreshData, 1000); delete yptr; return 0; }

When this program is run, the following messages are printed out:

   X's constructor invoked   Y's constructor invoked   Y's destructor invoked   X's destructor invoked 

Now the memory leak in Y is plugged. So the upshot is that if a destructor is not defined for a derived class, the system goes ahead and uses the base class destructor for destroying the base-class subobject inside the derived-class object. Not telling the system how to destroy the rest of a derived-class object can result in memory leaks if the derived-class object appropriates system resources apart from the system resources appropriated by any base-class subobjects contained therein.

The program output shown above demonstrates again that while a derived-class object is constructed base up, the destruction takes place in the reverse order. That is, the derived-class subobject is destroyed before the base-class subobject.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net