11.8 DESTRUCTORS IN C


11.8 DESTRUCTORS IN C++

As mentioned already in Chapter 3, when a newly created object goes out of scope, it is automatically destroyed in the memory by invoking its destructor. For example, consider the following program, in which we have first defined a class GameScore with two data members, homeTeamScore and otherTeamScore. We have also defined for this class a constructor and a destructor, the latter denoted GameScore(). The destructor is given the name of the class prefixed by a tilde. It can neither return a value, nor can it take any parameters.

Except for the message it prints out, the destructor provided in line (A) below is a do-nothing destructor. A trivial class like GameScore really does not need a programmer-defined destructor at all since it does not appropriate any system resources (such as memory) that would need to be freed up explicitly under program control prior to object destruction. Whatever memory is required for storing the two data members homeTeamScore and otherTeamScore would be automatically freed up when a GameScore object is destroyed. Nonetheless, the destructor shown in line (A) will help us shed light on some important properties of object destruction in C++.

 
//Destruct.cc #include <iostream> using namespace std; class GameScore { int homeTeamScore; int otherTeamScore; public: GameScore( int score1, int score2 ) { homeTeamScore = score1; otherTeamScore = score2; } ~GameScore() { //(A) cout << "GameScore object destroyed " << homeTeamScore << " vs. " << otherTeamScore << endl; } }; int main() { GameScore gs1( 28, 3 ); GameScore gs2( 35, 7 ); return 0; }

When this program is executed, the objects gs1 and gs2 created in main() will go out of scope when the flow of control hits the right brace of main(). As the two objects go out of scope, their destructors will be invoked. The result will be that the following message will appear on the terminal:

     GameScore object destroyed 35 vs. 7     GameScore object destroyed 28 vs. 3 

Note that the destructors are invoked in an order that is reverse of the order in which the objects are created. That is, the last object created will be the first to be destroyed.

Now let's examine a small variation on the example above. In main of the program below, in addition to the GameScore objects gs1 and gs2 created directly by the invocation of the constructor, let's have another object, p, of type GameScore* that points to a GameScore object created by using the operator new:

 
//Destruct2.cc #include <iostream> using namespace std; class GameScore { int homeTeamScore; int otherTeamScore; public: //constructor: GameScore( int score1, int score2 ) { homeTeamScore = score1; otherTeamScore = score2; } //destructor: ~GameScore() { cout << "GameScore Object destroyed" << homeTeamScore << " vs. " << otherTeamScore << endl; } }; int main() { GameScore gs1( 28, 3 ); GameScore gs2( 35, 7 ); GameScore* p = new GameScore( 29, 0 ); //(A) return 0; }

When this program is executed, we still get the same two lines of output as before:

     GameScore object destroyed  35 vs. 7     GameScore object destroyed  28 vs. 3 

In other words, the GameScore object pointed to by p in line (A) above is not getting destroyed. The reason for this is not too hard to understand. As the thread of execution hits the right brace of main(), what goes out of scope are the variables gs1, gs2 and p. That frees up the memory occupied by these three objects. With regard to the third object, note that only the memory where p is stored is freed up. But what's stored at this memory location is just the address of the last GameScore object. The end result is that the last GameScore object itself is not destroyed.

Objects that are created with the operator new can only be destroyed by applying the operator delete to the pointer returned by new. When either a reference or a pointer to a class type object goes out of scope, the object destructor is NOT automatically invoked.

To also free up the memory to which the variable p points in line (A) in the above program, we would have to change main so as to be

     int main()     {         GameScore gs1( 28, 3);         GameScore gs2( 35, 7);         GameScore* p = new GameScore( 29, 0);         delete p;         return 0;     } 

When the program shown previously is run with main() modified as above, the following messages are output on the terminal:

     GameScore object destroyed   35 vs. 7     GameScore object destroyed   28 vs. 3     GameScore object destroyed   29 vs. 0 

Instead of using the operator delete, we could also have destroyed the object pointed to by p by invoking the destructor directly, as in this version of main():

     int main()     {         GameScore gs1( 28, 3 );         GameScore gs2( 35, 7 );         GameScore* p = new GameScore( 29, 0 );         p->~GameScore();         return 0;     } 

This would produce exactly the same output as before.

The example we showed above is much too trivial, in the sense that the class GameScore as defined does not really need an explicitly defined destructor because the objects of this class do not appropriate any special resources, except for the memory needed for storing the values of the data members. The system-supplied default destructor for this class would work just fine in this case.

To consider a case when a programmer-supplied destructor is genuinely needed, here is an example in which the creation of an object entails appropriating a chunk of memory. All that is stored in a data member of the object is a pointer to this memory. Here is the code:

 
//DestructorNecessary.cc #include <iostream> using namespace std; class X { public: int* ptrToArray; int size; X( int* ptr, int sz ) : size(sz) { //(A) ptrToArray = new int[size]; //(B) for (int i = 0; i < size; i++) ptrToArray[i] = ptr[i]; //(C) } ~x() { //(D) cout << "hello from the destructor" << endl; delete [] ptrToArray; //(E) } }; int main() { int freshData[100] = {0}; X xobj(freshData, 100); //(F) X* p = new X(freshData, 100); //(G) delete p; //(H) return 0; }

The data member ptrToArray stores a pointer to the array for which memory is appropriated in the constructor in line (B). After acquiring the memory, the constructor copies over in line (C) its argument array into the freshly acquired memory pointed to by ptrToArray. The destructor, defined in line (D), invokes the delete[] operator in line (E) for releasing the memory that was appropriated by the constructor in line (B).

If you run this program, when the flow of execution hits the right brace of main, the destructor will be invoked for destroying the two X objects created in lines (F) and (G), for the latter through the invocation of delete in line (H). As a result, the following two messages will be printed out on the terminal:

     hello from the destructor     hello from the destructor 

In connection with explicit invocations of destructors, invoking a destructor when it should not be can lead to unpredictable behavior and often hard-to-locate bugs in computer programs. Consider the following program:

 
//DestructWhenNot.cc class X { public: int* ptrToArray; int size; X( int* ptr, int sz ) : size(sz) { ptrToArray = new int[size]; for ( int i = 0; i < size; i++ ) ptrToArray[i] = ptr[i]; } ~X() { delete [] ptrToArray; } }; int main() { int freshData[100] = {0}; X* p1 = new X( freshData, 100 ); X* p2 = new X( freshData, 100 ); delete p1; //(A) delete p1; // ERROR //(B) return 0; }

Here we have, presumably inadvertently, mistyped p2 as p1 in line (B) of main(). Inadvertently calling twice the destructor for p1 may or may not not be detected even at run time. But note that deleting an object twice is a serious error and can lead to unpredictable behavior, if not a memory segmentation fault. The second invocation of the destructor could delete the contents of some part of the memory that, after the first invocation in line (A), got put to use for something else.[7]

[7]As our examples have shown, most commonly a destructor is invoked implicitly when a class-type variable goes out of scope, or explicitly through the invocation of either the delete or the delete[] operator on a pointer acquired through the new or the new [] operator, respectively. However, as mentioned in the C++ standard [41, clause 12.4], it is legal to invoke a destructor explicitly on a class-type variable. But such direct invocations are rarely needed in practice. The standard shows an example related to memory management where such a direct invocation of a destructor could be useful.




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