Review

We started out this chapter by considering the problem of keeping track of all the “stuff” that we accumulate in our homes. This happens to make a good project to illustrate the use of the C++ tools that we've already covered, as well as to introduce a few new tools along the way. It also has the fortunate characteristic that the finished program might actually be useful.

Once we selected a project, the next step was to figure out exactly what the program should do.[15] In this case, we wanted to be able to keep track of the type of object, date of acquisition, price, and description for every item in the home, as well as any additional information needed for specific types of objects. An example is the CD, for which the additional information is the name of the artist and those of the tracks.

[15] This may seem too simple even to mention, but the failure to define exactly what a particular program is supposed to do is a major cause of wasted money and effort, especially in large corporations.

This led to the more general question of how to divide the nearly infinite number of real-world objects that we might encounter into the categories represented by a necessarily limited number of possible types of program objects. For example, what should we do with LPs or cassette tapes? Because they are used for much the same purpose as music CDs are, I decided to put all of these objects in the same “Music” type. A CD-ROM, on the other hand, is probably best represented by a “software” type, even though it is physically identical to a music CD, because it is used differently and has different information associated with it. For example, a CD-ROM often has a serial number whereas a music CD doesn't.

Having made a decision on this matter, we were ready to start designing the interface that the user of the HomeItem classes will see, using the manager/worker idiom to implement a polymorphic object solution to our problem.

Once we looked at the first version of the interface for the HomeItem class, the most obvious observation was that it is very similar to the StockItem class from previous chapters. The main reason for that similarity is that both of these classes play the role of the manager class in a polymorphic object implementation. One manager class looks very much like any other because a manager object's main duties are to create, destroy, and otherwise handle its worker objects; those duties are similar no matter what the worker objects actually do.

For this reason, all of the “structural” functions in the manager classes for the StockItem and HomeItem types are almost identical except for the exact data types of arguments and return values. These structural functions include the operators << and >>, the default constructors, copy constructors, “normal” constructors, assignment operators, and destructors. They also include the “special” constructor used to prevent an infinite loop during construction of a worker object and the Write function used to create a human-readable version of the data for an object.

By contrast, the “regular” member functions of each of these manager classes are whatever is needed to allow the users of that class to get their work done. These functions differ significantly from one polymorphic object implementation to another, as they are the most specific to the particular task of each class.

Upon examination, we saw that the same analogies apply to the worker classes for these two types as to the manager classes. The main difference between the StockItem worker classes and the first version of the HomeItem worker classes is that the HomeItem worker classes have a GetType member function that the StockItem worker classes don't have. This function allows the base class member data to be displayed with the correct tag indicating the exact type of the worker object being displayed, rather than requiring the duplication of code in the base and derived class output functions.[16]

[16] The use of a GetType member function to allow sharing of code for the base class member data display would probably have been a good idea for the StockItem classes as well, but I hadn't thought of it yet when I designed those classes.

Because this GetType function is used only within the implementation of the worker classes, it is first declared in HomeItemBasic rather than in HomeItem. This doesn't violate the principle that all objects of a polymorphic object type must have the same user interface, because GetType is not visible to the user of the HomeItem types and therefore doesn't change the user's view of these objects.

Next, we analyzed the first version of operator >> for the HomeItem classes. This function differs from the StockItem version of the same operator in a number of ways, the primary differences being its ability to skip blank lines in the input file, its deferred creation of variables until they are needed rather than all at the beginning of the function, and its use of an explicit type indicator (“Basic” or “Music”) to determine the type of the worker object, rather than relying on the value of the expiration date field as the operator >> for StockItem did.

The most generally applicable of these differences is the deferred creation of variables, which is a good way to save execution time and make the program easier to follow. In this case, we didn't create a Vec of track names until we had read the rest of the data for the object, including its type and track count; of course, a “Basic” object doesn't have the track count and track name fields anyway.

This implementation of operator >> also illustrates the new C++ feature that restricts the scope of a for index variable created in a for loop header to the controlled block of that for statement. Earlier versions of C++ allowed that variable to be accessed after the end of the for loop, an oversight that was corrected in the final C++ standard.

Then we moved on to the HomeItemBasic::Write function, which uses the GetType function to determine which type of object it is writing to the ostream specified by its argument. The HomeItemBasic version of Write has to call GetType because the derived class function HomeItemMusic::Write calls this function to do most of the work; thus, the object for which HomeItemBasic::Write has been called may be a derived class object rather than a HomeItemBasic object. This means that HomeItemBasic::Write has to call the virtual function GetType to find out the actual type of the object being written, so that it can write the correct type indicator to the output file along with the other data for the object. This is what makes it possible for us to reconstruct the object properly when we read the data back from the file.

When we got done with HomeItemBasic::Write, we continued by analyzing HomeItemMusic::Write. That function is pretty simple, except that it uses the size member function of the Vec data type to find out how many tracks are in the Vec so that it can write that track count, followed by all of the track names. This is necessary for us to reconstruct the “Music” object properly when we read its data from the file.

The next operation we undertook was to create a new HomeInventory class, which serves much the same function for HomeItems that the Inventory class does for StockItems; it allows us to create and keep track of a number of HomeItems and to search for a particular HomeItem.

The initial interface of this new HomeInventory class is also pretty simple, providing only the minimal set of operations we might want to use: loading the inventory from a disk file and searching for a particular item by name. To keep track of the number of elements in use, we decided to store the number of elements in the input file and creating a Vec of the correct size when we open the file. If we add new items to the Vec, we keep track of that fact via its size member function.

This solution eliminates the waste of time caused by resizing the Vec while reading data from the input file, but it also has a twist of its own: the need to read the data into a temporary holding variable so we can detect the end of the input file without running off the end of the Vec. This problem led to a discussion of the dangers of ignoring the possibility of errors in the data, as well as other sources of errors that lie outside the immediate scope of the code we write.

We continued by creating the ability for the user to enter data for a new object by adding an AddItem member function to the HomeInventory class and a NewItem static member function to the HomeItem class. The latter needs to be a static member function because it creates a new HomeItem object by reading data from the keyboard; it doesn't have an existing HomeItem object to work on, so it can't be a regular member function.

The implementation of HomeItem::NewItem is quite simple: it uses operator >> to read data from the keyboard into a newly created HomeItem object and then returns it to the caller. However, this works only because we changed operator >> to be usable for keyboard input by having it determine whether the user is typing at the keyboard. We do this by testing whether the input stream is cin. If so, the function displays prompts before each input operation; if not, the input operations are performed as they were previously.

We had to change operator >> rather than using it as previously implemented because adding another function for keyboard input would have required us to duplicate the code that reads the data, causing maintenance problems when any of those input operations had to be modified.

Then we added versions of FormattedDisplay to both HomeItemBasic and HomeItemMusic, which provide output virtually identical to the output of operator << for objects of those two types, except that FormattedDisplay also displays labels indicating what the data items represent.

The next order of business was to discuss GetName, whose only notable characteristic is that even though it is a virtual function, it is not implemented in HomeItemMusic because the implementation in HomeItemBasic will work perfectly for a HomeItemMusic object. As with a non-virtual function, if we call a virtual function for a derived class object that hasn't been defined in that class, the result will be to call the function in the nearest base class that defines the function — in this case HomeItemBasic::GetName.

Next we analyzed the test program hmtst4.cpp, which includes the new AddItem and GetName functions added since the last test program. We used these functions to add a new item and retrieve it, as well as to retrieve an item loaded from the file (just to make sure that still worked after all the changes we'd made).

In the next step, we added a mechanism to edit an object that already exists, via a new function called EditItem in the HomeInventory class and a corresponding function called Edit in the HomeItem class. We also added a helper function called LocateItemByName to the HomeInventory class to help us find an existing HomeItem to be edited.

We didn't have to change the test program very much from the previous version to accommodate this new ability to edit an existing object. However, we did have to modify the HomeItem classes significantly so that we could avoid keeping track of the field names in more than one function; this was intended to simplify maintenance later. One modification was the addition of a Read function that could fill in the data for an existing object rather than create the object directly in operator >>, as we had done until that point.

This revamping of the input mechanism required several new functions, which I added to HomeItemBasic and HomeItemMusic rather than to HomeItem, because they are used only as aids to the implementation of the new Read function. Included among these functions are ReadInteractive, ReadFromFile, EditField, and GetFieldName.

The last of these, GetFieldName, is particularly interesting on several counts. First, it uses a new construct, the enum, which is a way to define a number of constant values that are appropriate for naming array or Vec indexes. Second, it uses a static array of strings to hold the field names for use in prompts. This array is static so that it will be initialized only once, during the first call to GetFieldName, rather than every time the function is called. The reason we're using an array instead of a Vec is that it is (unfortunately) impossible in C++ to initialize a Vec from a list of values; this special C++ facility is available only for arrays. Because we're using an array, we have to check for the possibility of a bad index rather than rely on the safety checks built into a Vec; the program would fail in some mysterious way if we tried to access a nonexistent array element.

After some discussion of the properties of enums, including their ability to be converted to an integer type automatically when needed for arithmetic purposes, we continued with the implementation of Read, which merely decides whether the input is interactive and then calls the appropriate subfunction, ReadInteractive or ReadFromFile, accordingly. ReadInteractive, as its name implies, prompts the user for each field to be entered, using GetFieldName to retrieve the appropriate prompt for that field. ReadFromFile reads the same data but without displaying any prompts.

The other new function added to the HomeItem interface, Edit, first calls the updated version of FormattedDisplay to display the current data for the object to be edited, using GetFieldName to determine the prompt for each field to be displayed, and then calls EditField to request the new value for the field being modified. To simplify the code, this function uses a new construct, switch. This is essentially equivalent to a number of if and else statements that select one of a number of possible actions.

After finishing the changes to HomeItemBasic, we examined the corresponding functions in HomeItemMusic, which use the HomeItemBasic base class functions to do as much of the work as possible. For this reason, the new HomeItemMusic functions added no great complexity except for the necessity of handling a variable number of data elements in the track name Vec.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset