Previous Section Main Menu Next Section

CHAPTER 10

ANOTHER VISIT WITH THE CONTRACT PROGRAM
OR
WORKING WITH DYNAMICALLY SIZED LISTS

Section II: Declaring the New ListofContracts Section I: An Expandable List of Contracts Section III: Defining the New ListofContracts Section IV : Re-Evaluating the Contract Program Design
Section V: Adding String Properties to the Contracts Class Section VI: Declaring the New Contract Class Section VII: Defining the New Contract Class Section VIII: Modifying the Main Program


We have already noted the changes to the data members of the 'ListOfContracts' class - we will use the variable name 'presentMax' in place of 'limit' and 'listOfContracts' will be a pointer to an array instead of an array itself. This last change means we no longer need a user defined data type for the array. Nor, do we need the constant indicating the number of elements in the array. However, we decided to use a constant to indicate by how much the array should grow each time it gets full.

Of the three functions we designed algorithms for in the previous sections, two of them, the constructor and 'Add', already exist in the class declarations. True, we will need to modify their code but the class declarations concern themselves only with what not how. Therefore, since there are no changes in the receives and returns for these functions, no changes need be made in their declarations. We do need to declare the third function - the one that will increase the size of the array. Note that this is another one of those functions that only instances of the class need. Like the already existing search function, users of this class will not directly call this new function and therefore, like 'Search', it should be declared in the private part of the class. Here is the code:

Before we review the key points of this code, note the additional comments. It is not enough to include the "Purpose, Goal, Receive and Return" information. In fact, the code examples are already starting to drop this in hope that you are coming to understand the need to do this analysis on your own. What is still needed, now even more than ever, are comments that explain key or complex parts of the code. Thus the code includes descriptions of key variables, constants and lines of code as well as algorithms for complex functions.

The code itself follows the discussion we just had. To review:

  1. There is no 'typedef' at the top because there is no need for an array type

  2. The constant has changed its name and meaning. The comment next to it explains its new purpose. Note that the value of this constant is small to make it easy to have enough contracts to test the 'IncreaseSize' function. In the final version this value would probably be greater.

  3. The data member 'listOfContracts' is now simply a pointer to a 'Contract'. Remember that this is how one declares a pointer to an array - you declare a pointer to the type of the elements in the array.

The function 'IncreaseSize is declared in the private section. Note that it has no parameters and returns nothing. One might question this because certainly 'IncreaseSize' needs the array of contracts if it is going to change its size. However, 'IncreaseSize' is part of the class. It, therefore, has direct access to the data members of the instances of the class, including 'lisOfContracts'- the data member that points to the array.

Anytime the function member 'Add' is called, it will be associated with a specific instance of the class 'ListOfContracts. If 'Add' then calls 'IncreaseSize', 'IncreaseSize' will have access to the data members of the same instance, including 'listOfContracts'. Since it has access to these data members, it does not need to have them passed as parameters.  

There is one new piece to this class declaration that we have not yet discussed. We know that all classes have at least one constructor. They also have a destructor - a function that gets rid of (destroys) instances when they are no longer needed. What this means is that the memory used for the data members of the instance is returned to the system. Destructor functions are often not explicitly called in programs. Instead, they are called implicitly by the program when an instance goes out of existence. For example, when we have declared a class instance as a local variable of a function, that instance is destroyed when the function returns. - just as all other local variables are destroyed. All this takes place automatically.

As with constructors, if we do not declare a destructor, the system declares one for us. As long as we do not want anything special to happen when an instance is destroyed, the default destructor is just fine. This is usually the case when the data members of the class do not include pointers. That is why we have not had to worry about destructors up to now - our classes did not use pointers.

However, there is a problem when one or more of the data members is a pointer. The default destructor returns the memory used by the pointer itself but NOT the memory pointed to by the pointer. Consider an instance of 'ListOfContracts'. Here is a picture of the memory involved in such an instance.


Figure 10.5

If the destructor for this instance is called, the memory used by 'numContracts', presentMax, and the pointer 'listOfContracts' would be returned to the system. However, the destructor knows nothing about the array being pointed to by 'listOfContracts' so its memory would not be returned. Here is how memory would look in such a situation. Note that the array is out there but there is no way to access it. This is an example of a memory leak.


Figure 10.6

To get this memory returned, we need to explicitly declare and define a destructor that will use a built-in operator that is the opposite of 'new' - the delete operator. This operator takes a pointer and returns to the system whatever memory is being pointed to. For example, if we have the code:

int* ptr;
ptr = new int;
*ptr = 5;

memory would look as follows:


Figure 10.7

If the program later has the line:

delete ptr;

memory will look as follows:


Figure 10.8

Note that the memory for the variable 'ptr' still exists but not the memory it pointed to - the memory that used to hold the value 5. Note further that although the memory no longer exists (actually it is on the heap available to be used by another request), the memory location 'ptr' still acts as if it is pointing to it. Thus, if 'ptr' is used incorrectly, there could be problems.

Deleting the memory for an array is only slightly more complex.

Consider this modified version of the code we just saw:

Memory would look as follows:


Figure 10.9

Although the code says ptr points to an integer, we know that 'ptr' actually points to an array of integers. It is relatively easy for the system to keep track of this fact and thus return all the memory allocated. As long as we are dealing with an array of integers, doubles, chars, bool, or some simple user defined type, there is no problem. However, there is a problem when we have an array of some class type and that class type has a non-default destructor. Now, that destructor needs to be called separately for each element of the array.

For example, suppose Olympia wants a separate list of contracts for each year she has been in business. We could implement this with an array of type 'ListOfContracts'. Further, suppose that we declare this array as a local variable in some function in our program. When that function finishes, the array represented by the local variable is destroyed and, in the process, the destructor for 'ListOfContracts' must be called for each element of the array.

At different stages in its development, C++ has had different ways of handling this. At first special code - indicating how many elements there were in the array - was only required when the array held instances of a class for which a specific destructor had been defined. Other methodologies were proposed, and you would not believe the amount of argument that surrounds details such as this. In any case, the latest approach and the one used in the latest compilers says that any time a pointer points to an array, the programmer should use 'delete' followed by a empty pair of brackets, followed by the pointer. This tells the system that there is an array of something to be deleted and it handles determining if a destructor needs to be called and, if so, how often.

Thus to delete the memory accessed by 'ptr' above we would write:

delete [] ptr;

******************

Back to destructors: Since 'ListOfContracts' includes a pointer to an array of contracts, we need a destructor to delete the array. To declare a destructor for a class, one simply puts a tilde (~) in front of the class name. Like constructors, destructors cannot return anything and the declaration code does not even include the word 'void'. Also, destructors cannot receive anything so they have no parameters. The 'Contract' class, as presently designed, does not need an explicitly declared destructor but if we wanted one, we would declare it as:

~Contract();

Notice that what distinguishes a classes destructor from a constructor with no parameters is the tilde. To declare the destructor for ListOfContracts, we follow the same procedure and write:

~ListOfContracts();

Topics Covered in the "Essentials of C++"

Destructors
Returning Memory to the Heap

Top of Section Main Menu Next Section