Reference Arguments

As you may recall from Chapter 5, when we call a function, the arguments to that function are actually copies of the data supplied by the calling function; that is, a new local variable is created and initialized to the value of each expression from the calling function and the called function works on that local variable. Such a local variable is called a value argument, because it is a new variable with the same value as the caller's original argument. There's nothing wrong with this in many cases, but sometimes, as in the present situation, we have to do it a bit differently. A reference argument, such as the istream& argument to Read, is not a copy of the caller's argument, but another name for the actual argument passed by the caller.

Reference arguments are often more efficient than value arguments, because the overhead of making a copy for the called function is avoided. Another difference between value arguments and reference arguments is that any changes made to a reference argument change the caller's actual argument as well, which in turn means that the caller's actual argument must be a variable, not an expression like x + 3; changing the value of such an expression wouldn't make much sense. This characteristic of reference arguments can confuse the readers of the calling function; there's no way to tell just by looking at the calling function that some of its variables can be changed by calling another function. This means that we should limit the use of reference arguments to those cases where they are necessary.

In this case, however, it is necessary to change the stream object that is the actual argument to the Read function, because that object contains the information about what data we've already read from the stream. If we passed the stream as a value argument, then the internal state of the "real" stream in the calling function wouldn't be altered to reflect the data we've read in our Read function, so every time we called Read, we would get the same input again. Therefore, we have to pass the stream as a reference argument.

The complete decoding of the function declaration void StockItem::Read(istream& s) is shown in Figure 6.14. Putting it all together: we're defining a void function (one that doesn't return a value), called Read, which belongs to class StockItem. This function takes an argument named s that's a reference to an istream. That is, the argument s is another name for the istream passed to us by the caller, not a copy of the caller's istream.

Figure 6.14. The declaration of StockItem::Read in codeitem2.h


As you probably have guessed, Susan had some questions about this whole concept.

Susan: How does Read make Shopinfo go get data?

Steve: Well, the argument s is a reference to the stream object provided by the caller; in this case, Shopinfo. That stream is connected to the file shop2.in.

Susan: Ok, but in the test program, Shopinfo, which is an ifstream, is passed as an argument to the Read member function in the StockItem class. But Read(istream&) function in the StockItem class expects a reference to an istream as an argument. I understand the code of both functions, but I don't see how you can pass an ifstream as an istream. As far as I know, we use fstreams to write to and read from files and istream and ostream to read from the keyboard and write to the screen. So, can you mix these when you pass them on as arguments?

Steve: Yes. As we'll see later, this is legal because of the relationship between the ifstream and istream classes.[16]

[16] As explained in the footnote on page 609.

Susan: How does Read do the reading? How come you are using >> without a cin statement?

Steve: cin isn't a statement, but a stream that is created automatically whenever we #include <iostream> in our source file. Therefore, we can read from it without connecting it to a file. In this case, we're reading from a different stream, namely s.

Susan: How come this is a void type? I would think it would return data being read from a file.

Steve: You would think so, wouldn't you? I love it when you're logical. However, what it actually does is to read data from a file into the object for which it was called. Therefore, it doesn't need a return value.

Susan: So the ifstream object is a transfer mechanism? That is, ifstream s; would read data from a file named s?

Steve: Yes, ifstream is a transfer mechanism. However, ifstream s; would create an ifstream called s that was not connected to any file; the file could be specified later. If we wanted to create an ifstream called s that was connected to a file called xyz, then we would write ifstream s("xyz");.

Susan: OK. An ifstream just reads data from a file. It doesn't care which file, until you specify it?

Steve: Right.

Susan: What does this mean without cin? Is it just the same thing, only you can't call it cin because cin is for native use and this is a class? How come the >> is preceded by the argument s?

Steve: The s takes the place of cin, because we want to read from the stream s, not the stream cin, which is a stream that is connected to the keyboard. Whatever is typed on the keyboard goes into cin, whereas the source for other streams depends on how they are set up. For example, in this program, we have connected the stream called s to a file called “shop2.in”.

Susan: Tell me what you mean by "just a stream".

Steve: Think of it like a real stream, with the bytes as little barges that float downstream. Isn't that poetic? Anyway, there are three predefined streams that we get automatically when we #include <iostream>: cin, cout, and cerr. The first two we've already seen, and the last one is intended for use in displaying error messages.

There is one point that we haven't examined yet, though, which is how this routine determines that it's finished reading from the input file. With keyboard input, we process each line separately after the user hits ENTER, but that won't do the job with a file, where we want to read all the items in until we get to the end of the file. We actually handle this detail in the main program itemtst2.cpp (Figure 6.11 on page 341) by asking ShopInfo whether there is any data left in the file; to be more precise, we call the ifstream member function fail() to ask the ShopInfo ifstream whether we have tried to read past the end of the file. If we have, then the result of that call to ShopInfo.fail() will be nonzero (which signifies true). If we haven't yet tried to read past the end of the file, then the result of that call will be 0 (which signifies false). How do we use this information?

We use it to decide whether to execute a break statement. This is a loop control device that interrupts processing of a loop whenever it is executed. The flow of control passes to the next statement after the end of the controlled block of the for statement.[17]

[17] The break statement can also terminate execution of a while loop, as well as another type of control mechanism that we'll cover in Chapter 11.

The loop will terminate in one of two ways. Either 100 records have been read, in which case i will be 100; or the end of the file is reached, in which case i is the number of records that have been read successfully.

Susan had some questions about the implementation of this program.

Susan: What is fail()?

Steve: It's a member function of the ifstream class.

Susan: Where did it come from?

Steve: From the standard library.

Susan: But it could be used in other classes, right?

Steve: Not unless they define it as well.[18]

[18] Actually, this isn't quite true. As we'll see in Chapter 9, there are mechanisms in C++ that will allow us to reuse functionality from existing classes when we create our own new classes.

Susan: How does all the data having been read translate into "nonzero"? What makes a "nonzero" value true?

Steve: That's a convention used by that function.

Susan: So anything other than 0 is considered true?

Steve: Yes.

Susan: Where did break come from?

Steve: It's another keyword like for; it means to terminate the loop that is in progress.

Susan: I do not understand what is actually happening with the program at this time. When is break implemented? Is it just to end the reading of the entire file?

Steve: We have to stop reading data when there is no more data in the file. The break statement allows us to terminate the loop when that occurs.

Susan: What do you mean that the loop will terminate either by 100 records being read or when the end of the file is reached? Isn't that the same thing?

Steve: It's the same thing only if there are exactly 100 records in the file.

Susan: So you mean when there are no more records to be read? So that the loop won't continue on till the end with nothing to do?

Steve: Exactly.

Susan: So does i just represent the number of records in the file?

Steve: Actually, it's the number of records that we've read.

Susan: Does this library have a card catalogue? I would like to know what else is in there.

Steve: There is a library reference manual for most libraries. If you get a library with a commercial compiler, that manual comes with the compiler documentation; otherwise, it's usually an on-line reference (that is, a help file). There's also quite a good book about the C++ standard library, with the very imaginative name The C++ Standard Library, written by Nicolai Josuttis (ISBN 0-201-37926-0). Every serious C++ programmer should have a copy of that book.

Susan: A novice would not know this. Put it in the book.

Steve: Done.

Susan: Well, the program sounded like that indeed there were 100 records in the file. However, I see that in practice that might change, and why you would therefore need to have a break.

Steve: You obviously understand this.

Whether there are 100 records in the file or fewer than that number, obviously the number of items in the Vec is equal to the current value of i. Or is it?

Fencepost Errors

Let's examine this a bit more closely. You might be surprised at how easy it is to make a mistake in counting objects when writing a program. The most common error of this type is thinking you have one more or one less than the actual number of objects. In fact, this error is common enough to have a couple of widely known nicknames: off by one error and fencepost error. The former name should be fairly evident, but the latter name may require some explanation. First, let's try it as a "word problem". If you have to put up a fence 100 feet long and each section of the fence is 10 feet long, how many sections of fence do you need? Obviously, the answer is 10. Now, how many fenceposts do you need? 11. The confusion caused by counting fenceposts when you should be counting segments of the fence (or vice versa) is the cause of a fencepost error.

That's fine as a general rule, but what about the specific example of counting records in our file? Well, let's start out by supposing that we have an empty file, so the sequence of events in the first loop in the current main program (Figure 6.11 on page 341) is as follows:

1.
Set i to 0.

2.
Is i less than 100? If not, exit. If so, continue.

3.
Use the Read function to try to read a record into the ith element of the AllItems Vec.

4.
Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.

5.
If so, execute the break statement to exit the loop.

The answer to the question in step 4 is that in fact nothing was read, so we do execute the break and leave the loop. The value of i is clearly 0 here, because we never went back to the top of the loop; since we haven't read any records, setting InventoryCount to i works in this case.

Now let's try the same thing, but this time assuming that there is one record in the file. Here's the sequence of events:

1.
Set i to 0.

2.
Is i less than 100? If not, exit. If so, continue.

3.
Use the Read function to try to read a record into the ith element of the AllItems Vec.

4.
Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.

5.
If so, execute the break statement to exit the loop. In this case, we haven't run off the end of the file, so we go back to the top of the loop, and continue as follows:

6.
Increment i to 1.

7.
Is i less than 100? If not, exit. If so, continue.

8.
Call Read to try to read a record into the AllItems Vec.

9.
Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.

10.
If so, execute the break statement to exit the loop.

The second time through, we execute the break. Since i is 1, and the number of elements read was also 1, it's correct to set the count of elements to i.

It should be pretty clear that this same logic applies to all the possible numbers of elements up to 99. But what if we have 100 elements in the file? Relax, I'm not going to go through these steps 100 times, but I think we should start out from the situation that would exist after reading 99 elements and see if we get the right answer in this case, too. After the 99th element has been read, i will be 99; we know this from our previous analysis that indicates that whenever we start executing the statements in the controlled block of the loop, i is always equal to the number of elements previously read. So here's the 100th iteration of the loop:

1.
Call Read to try to read a record into the AllItems array.

2.
Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.

3.
If so, execute the break statement to exit the loop.

4.
Otherwise, increment i to 100.

5.
Is i less than 100? If not, exit. If so, continue.

6.
Since i is not less than 100, we exit.

At this point, we've read 100 records and i is 100, so these two numbers are still the same. Therefore, we can conclude that setting InventoryCount equal to i when the loop is finished is correct; we have no fencepost error here.

Susan wasn't sure why I was hammering this fencepost thing into the ground:

Susan: Why are you always saying that "it's correct to set the count of elements to i"?

Steve: Because I'm showing how to tell whether or not we have a fencepost error. That requires a lot of analysis.

Actually, this whole procedure we've just been through reminds me of the professor who claimed that some point he was making was obvious. This was questioned by a student, so the professor spent 10 minutes absorbed in calculation and finally emerged triumphantly with the news that it was indeed obvious.

Assuming that you've installed the software from the CD in the back of this book, you can try out this program. First, you have to compile it by following the compilation instructions on the CD. Then type itemtst2 to run the program. You'll see that it indeed prints out the information from the StockItem objects. You can also run it under the debugger by following the usual instructions for that method.

Other Possible Transactions with the Inventory

Of course, this isn't all we want to do with the items in the store's inventory. Since we have a working means of reading and displaying the items, let's see what else we might want to do with them. Here are a few possible transactions at the grocery store:

  1. George comes in and buys 3 bags of marshmallows. We have to adjust the inventory for the sale.

  2. Sam wants to know the price of a can of string beans.

  3. Judy comes in looking for chunky chicken soup; there's none on the shelf where it should be, so we have to check the inventory to see if we're supposed to have any.

All of these scenarios require the ability to find a StockItem object given some information about it. Let's start with the first example, which we might state as a programming task in the following manner: "Given the UPC from the bag of marshmallows and the number of bags purchased, adjust the inventory by subtracting the number purchased from the previous quantity on hand." Figure 6.15 is a program intended to solve this problem.

Figure 6.15. First attempt to update inventory of StockItems (codeitemtst3.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "Vec.h"
#include "item2.h"
using namespace std;

int main()
{
   ifstream ShopInfo("shop2.in");
   Vec<StockItem> AllItems(100);
   short i;
   short InventoryCount;
   string PurchaseUPC;
   short PurchaseCount;
   bool Found;

   for (i = 0; i < 100; i ++)
      {
      AllItems[i].Read(ShopInfo);
      if (ShopInfo.fail() != 0)
          break;
      }

   InventoryCount = i;

   cout << "What is the UPC of the item?" << endl;
   cin >> PurchaseUPC;
   cout << "How many items were sold?" << endl;
   cin >> PurchaseCount;

   Found = false;
   for (i = 0; i < InventoryCount; i ++)
      {
      if (PurchaseUPC == AllItems[i].m_UPC)
         {
         Found = true;
         break;
         }
      }

   if (Found)
       {
       AllItems[i].m_InStock -= PurchaseCount;
       cout << "The inventory has been updated." << endl;
       }
   else
       cout << "Can't find that item. Please check UPC" << endl;

   return 0;
}

Here is a more detailed analysis of the steps that the program in Figure 6.15 is intended to perform:

1.
Take the UPC from the item.

2.
For every item in the inventory list, check whether its UPC is the same as the one from the item.

3.
If it doesn't match, go back to step 2.

4.
If it does match, subtract the number purchased from the inventory.

There's nothing really new here except for the bool variable type, which we'll get to in a moment, and the -= operator that the program uses to adjust the inventory. -= is just like +=, except that it subtracts the right-hand value from the left-hand variable, while += adds.

The bool variable type is a relatively new addition to C++ that was added to C++ in the process of developing the standard and is available on any compiler that conforms to the standard. Expressions and variables of this type are limited to the two values true and false.[19] We've been using the terms true and false to refer to the result of a logical expression such as if (x < y); similarly, a bool variable or function return value can be either true or false.

[19] The type bool is short for "boolean", which means "either true or false". The derivation of the term "boolean" is interesting but not relevant here.

Attempting to Access private Member Variables

If you compile the program in Figure 6.15, you'll find that it is not valid. The problem is the lines:

if (PurchaseUPC == AllItems[i].m_UPC)

and

AllItems[i].m_InStock -= PurchaseCount;

The first of these lines could be translated into English as follows:

"if the input UPC is the same as the value of the m_UPC member variable of the object stored in the ith element of the AllItems Vec, then..."

while the second of these lines could be translated as:

"subtract the number of items purchased from the value of the m_InStock member variable of the object stored in the ith element of the AllItems Vec".

While both of these lines are quite understandable to the compiler, they are also illegal because they are trying to access private member variables of the StockItem class, namely m_UPC and m_InStock, from function main. Since main is not a member function of StockItem, this is not allowed. The error message from the compiler should look something like Figure 6.16.

Figure 6.16. Unauthorized access prohibited
ITEMTST3.cpp:
Error E2247 ITEMTST3.cpp 36: 'StockItem::m_UPC' is not accessible in function main()
Error E2247 ITEMTST3.cpp 45: 'StockItem::m_InStock' is not accessible in function main()
*** 2 errors in Compile ***

Does this mean that we can't accomplish our goal of updating the inventory? Not at all. It merely means that we have to do things "by the book" rather than going in directly and reading or changing member variables that belong to the StockItem class. Of course, we could theoretically "solve" this access problem by simply making these member variables public rather than private. However, this would allow anyone to mess around with the internal variables in our StockItem objects, which would defeat one of the main purposes of using class objects in the first place: that they behave like native types as far as their users are concerned. We want the users of this class to ignore the internal workings of its objects and merely use them according to their externally defined interface; the implementation of the class is our responsibility, not theirs.

This notion of implementation being separated from interface led to an excellent question from Susan:

Susan: Please explain to me why you needed to list those member variables as private in the interface of StockItem. Actually, why do they even need to be there at all? Well, I guess you are telling the compiler that whenever it sees the member variables that they will always have to be treated privately?

Steve: They have to be there so that the compiler can figure out how large an object of that class is. Many people, including myself, consider this a flaw in the language design because private variables should really be private, not exposed to the class user.

Obviously, she'd lost her true novice status by this point. Six months after finding out what a compiler is, she was questioning the design decisions made by the inventor of C++; what is more, her objections were quite well founded.

As it happens, we can easily solve our access problem without exposing the implementation of our class to the user any more than it already has been by virtue of the header file. All we have to do is to add a couple of new member functions called CheckUPC and DeductSaleFromInventory to the StockItem class; the first of these allows us to check whether a given UPC belongs to a given StockItem, and the second allows us to adjust the inventory level of an item.

Susan had another suggestion as to how to solve this problem, as well as a question about why I hadn't anticipated it in the first place:

Susan: Hey, wouldn't it be easier to write a special main that is a member function to get around this?

Steve: That's an interesting idea, but it wouldn't work. For one thing, main is never a member function; this is reasonable when you consider that you generally have quite a few classes in a program. Which one would main be a member function of?

Susan: So then all these new member functions do is to act as a gobetween linking the StockItem class and the inventory update program to compare data that is privately held in the StockItem class?

Steve: Yes, the new entries in the interface are designed to make the private data available in a safe manner. I think that's the same as what you're saying.

Susan: If you wanted to change the program, why didn't you just do it in the first place instead of breaking it down in parts like this?

Steve: Because that's not the way it actually happens in real life.

Susan: Do you think it less confusing to do that, and also does this act as an example of how you can modify a program as you see the need to do it?

Steve: Right on both counts.

Figure 6.17 shows the new, improved interface definition.

Figure 6.17. An enhanced interface for the StockItem class (codeitem4.h)
class StockItem
{
public:
  StockItem();

  StockItem(std::string Name, short InStock, short Price,
  std::string Distributor, std::string UPC);

  void Display();
  void Read(std::istream& is);

  bool CheckUPC(std::string ItemUPC);
  void DeductSaleFromInventory(short QuantitySold);
  short GetInventory();
  std::string GetName();

private:
   short m_InStock;
   short m_Price;
   std::string m_Name;
   std::string m_Distributor;
   std::string m_UPC;
};

I recommend that you print out the files that contain this interface and its implementation as well as the test program, for reference as you are going through this part of the chapter; those files are item4.h, item4.cpp, and itemtst4.cpp, respectively. The declarations of the two new functions, CheckUPC and DeductSaleFromInventory, should be pretty easy to figure out: CheckUPC takes the UPC that we want to find and compares it to the UPC in its StockItem, then returns true if they match and false if they don't. Here's another good use for the bool data type: the only possible results of the CheckUPC function are that the UPC in the StockItem matches the one we've supplied (in which case we return true) or it doesn't match (in which case we return false). DeductSaleFromInventory takes the number of items sold and subtracts it from the previous inventory. But where did GetInventory and GetName come from?

Making the Program More User-friendly

I added those functions because I noticed that the "itemtst" program wasn't very user-friendly. Originally it followed these steps:

1.
Ask for the UPC.

2.
Ask for the number of items purchased.

3.
Search through the list to see whether the UPC is legitimate.

4.
If so, adjust the inventory.

5.
If not, give an error message.

6.
Exit.

What's wrong with this picture? Well, for one thing, why should the program make me type in the number of items sold if the UPC is no good? Also, it never told me the new inventory or even what the name of the item was. It may have known these things, but it never bothered to inform me. So I changed the program to work as follows:

1.
Ask for the UPC.

2.
Search through the list to see whether the UPC was legitimate.

3.
If not, give an error message and exit.

4.
If the UPC was OK, then

a. Display the name of the item and the number in stock.

b. Ask for the number of items purchased.

c. Adjust the inventory.

d. Display a message with the name of the item and number of remaining units in inventory.

5.
Exit.

To do this, I needed those two new functions GetInventory and GetName, so as you've seen I added them to the class declaration. Figures 6.18-6.21 show the implementation of all the new functions.

Figure 6.18. StockItem::CheckUPC (from codeitem4.cpp)
bool StockItem::CheckUPC(string ItemUPC)
{
 if (m_UPC == ItemUPC)
    return true;
 else
    return false;
}

Figure 6.19. StockItem::DeductSaleFromInventory (from codeitem4.cpp)
void StockItem::DeductSaleFromInventory(short QuantitySold)
{
 m_InStock -= QuantitySold;
}

Figure 6.20. StockItem::GetInventory (from codeitem4.cpp)
short StockItem::GetInventory()
{
 return m_InStock;
}

Figure 6.21. StockItem::GetName (from codeitem4.cpp)
string StockItem::GetName()
{
 return m_Name;
}

Our current itemtst example is getting to be enough like a real program that I'm going to start using the term application program (or equivalently, application) to refer to it sometimes. As is generally true of C++ programs, the responsibility for doing the user's work is divided up into a main program (or application program) and a set of classes (sometimes called infrastructure) used in the application. In this case, itemtst4.cpp is the main program, or application program, whereas the other two files (item4.h and item4.cpp) are the infrastructure. Figure 6.22 shows the new, improved version of our application, which updates the inventory and actually tells the user what it's doing.

Figure 6.22. Updating StockItem inventory (codeitemtst4.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "Vec.h"
#include "item4.h"
using namespace std;

int main()
{
   ifstream ShopInfo("shop2.in");
   Vec<StockItem> AllItems(100);
   short i;
   short InventoryCount;
   short OldInventory;
   short NewInventory;
   string PurchaseUPC;
   string ItemName;
   short PurchaseCount;
   bool Found;

   for (i = 0; i < 100; i ++)
      {
      AllItems[i].Read(ShopInfo);
      if (ShopInfo.fail() != 0)
          break;
      }

   InventoryCount = i;
   cout << "What is the UPC of the item? ";
   cin >> PurchaseUPC;
   Found = false;

   for (i = 0; i < InventoryCount; i ++)
      {
      if (AllItems[i].CheckUPC(PurchaseUPC))
          {
          Found = true;
          break;
          }
      }

   if (Found)
      {
      OldInventory = AllItems[i].GetInventory();
      ItemName = AllItems[i].GetName();

      cout << "There are currently " << OldInventory << " units of "
      << ItemName << " in stock." << endl;
      cout << "How many items were sold? ";
      cin >> PurchaseCount;

      AllItems[i].DeductSaleFromInventory(PurchaseCount);
      cout << "The inventory has been updated." << endl;

      NewInventory = AllItems[i].GetInventory();
      cout << "There are now " << NewInventory << " units of "
      << ItemName << " in stock." << endl;
      }
   else
      cout << "Can't find that item. Please check UPC" << endl;

   return 0;
}

This code should be pretty easy to follow; it simply implements the first item purchase scenario I outlined in the list on page 356. Assuming that you've installed the software from the CD in the back of this book, you can try out this program. First, you have to compile it by following the compilation instructions on the CD. Then type itemtst4 to run the program. You can also run it under the debugger by following the usual instructions for that method.

In either case, the program will start up and ask you for the UPC; you can use 7904886261, which is the (made-up) UPC for "antihistamines". Type in that number and hit ENTER.

The Inventory class

Now let's consider what might be needed to handle some of the other possibilities, starting with the second scenario in that same list. To refresh your memory, here it is again: "Sam wants to know the price of a can of string beans". A possible way to express this as a programming task is "Given a UPC, look up the price of the item in the inventory".

Here is a set of steps to solve this problem:

1.
Ask for the UPC.

2.
Search through the list to see whether the UPC is legitimate.

3.
If not, give an error message and exit.

4.
If the UPC is OK, then display the name and price of the item.

5.
Exit.

Have you noticed that this solution is very similar to the solution to the first problem? For example, the search for an item with a given UPC is exactly the same. It seems wasteful to duplicate code rather than using the same code again, and in fact we've seen how to avoid code duplication by using a function. Now that we're doing "object-oriented" programming, perhaps this new search function should be a member function instead of a global one.

This is a good idea, except that the search function can't be a member function of StockItem, because we don't have the right StockItem yet; if we did, we wouldn't need to search for it. Therefore, we have to create a new class that contains a member variable that is a Vec of StockItems and write the search routine as a member function of this new class; the new member function would look through its Vec to find the StockItem we want. Then we can use the member functions of StockItem to do the rest. Figure 6.23 shows the interface (class declaration) for this new class, called Inventory.

Figure 6.23. Interface of Inventory class (codeinvent1.h)
class Inventory
{
public:
  Inventory();

  short LoadInventory(std::ifstream& is);
  StockItem FindItem(std::string UPC);
  bool UpdateItem(StockItem Item);
private:
   Vec<StockItem> m_Stock;
   short m_StockCount;
};

I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this chapter; those files are invent1.h, invent1.cpp, and itemtst5.cpp, respectively.

Susan was somewhat surprised that I would even consider writing a global function to find a StockItem:

Susan: What do you mean by making this a member function instead of a global function? When was it ever a global function?

Steve: It wasn't any kind of function before. However, making it a function would make sense; the question is what kind of function, global or member?

Susan: I am not sure if I truly understand the problem as to why you can't search StockItem as a member function.

Steve: A member function of StockItem always accesses a particular StockItem. However, our problem is that we don't know which StockItem we want; therefore, a member function, which must apply to a particular StockItem, won't solve our problem.

Susan: OK, Stevie, here is the deal. Why would you even consider making this a global function? Of course it is a member function. We are doing object oriented programming, aren't we?

Steve: Aren't we knowledgeable all of a sudden? Who was that person who knew nothing about programming eight months ago?

Susan: You've got me there. But seriously, what would be the advantage of making it a global function rather than a member function? This is what has me bothered about the whole thing.

Steve: There wouldn't be any advantage. I just wanted to point out that it clearly can't be a member function of StockItem, and indicate the possible alternatives.

Susan: Oh, then so far that is all our program is able to do? It is unable to locate one item of all possible items and display it just from the UPC code? In fact that is what we are trying to accomplish, right?

Steve: Exactly.

Susan also wasn't sure why we needed LoadInventory.

Susan: What does the code short LoadInventory (ifstream& is); do? Does it just give you an object named LoadInventory that reads a file that has a reference argument named is? I don't get this.

Steve: That's quite close. The line you're referring to is the declaration of a function named LoadInventory, which takes a reference to an ifstream. The implementation of the function, as you'll see shortly, reads StockItem records from the file connected to the ifstream.

Once that was cleared up, she had some questions about the way the FindItem function works, including its interface.

Susan: Is the argument UPC to the FindItem function a string because it is returning the name of a stock item?

Steve: That argument is the input to the FindItem function, not its output; therefore, it's not "returning" anything. FindItem returns the StockItem that it finds. Or did I misunderstand your question?

Susan: Let's see if I even know what I was asking here. OK, how about this: I wanted to know why UPC was a string and not a short, since a UPC is usually a number. In this case, it will be returning a name of a "found item" so that is why it is a string, right?

Steve: No, it's because the UPC won't fit in any of the types of numbers we have available. Thus, the most sensible way to store it is as a string. Since we don't use it in calculations anyway, the fact that you can't calculate with string variables isn't much of a restriction.

Susan: Oh. OK. So a string is more useful for storing numbers that are somewhat lengthy as long as you don't calculate with those numbers. They are nothing more than "numerical words"?

Steve: Exactly.

Most of this should be fairly self-explanatory by this point. We start out with the default constructor which makes an empty Inventory. Figure 6.24 has the implementation for the default constructor.[20]

[20] As before, we can count on the compiler to supply the other three standard member functions needed for a concrete data type: the copy constructor, the assignment operator =, and the destructor.

Figure 6.24. Default constructor for Inventory class (from codeinvent1.cpp)
Inventory::Inventory()
: m_Stock (Vec<StockItem>(100)),
  m_StockCount(0)
{
}

There's nothing complex here; we're using the member initialization list to initialize the m_Stock variable to a newly constructed Vec of 100 StockItems and the number of active StockItems to 0. The latter, of course, is because we haven't yet read any data in from the file.

Then we have a couple of handy functions. The first is LoadInventory, which will take data from an ifstream and store it in its Inventory object, just as we did with the AllItems Vec in our application itemtst4.cpp.

Susan had a question about this:

Susan: How did you know that you were going to need to use an ifstream again?

Steve: Because we're reading data from a file into a Vec of StockItems, and reading data from a file is what ifstreams are for.

Figure 6.25 shows the implementation of LoadInventory.

Figure 6.25. LoadInventory function for the Inventory class (from codeinvent1.cpp)
short Inventory::LoadInventory(ifstream& is)
{
  short i;

  for (i = 0; i < 100; i ++)
     {
     m_Stock[i].Read(is);
     if (is.fail() != 0)
         break;
     }

  m_StockCount = i;
  return m_StockCount;
}

Now we come to the FindItem member function. Its declaration is pretty simple: it takes an argument of type string which contains the UPC that we're looking for. Its implementation should be pretty simple, too: it will search the Inventory object for the StockItem that has that UPC and return a copy of that StockItem, which can then be interrogated to find the price or whatever other information we need.

However, there's a serious design issue here: what should this function return if the UPC doesn't match the UPC in any of the StockItem entries in the Inventory object? The application program has to be able to determine whether or not the UPC is found. In the original program this was no problem, because the main program maintained that information itself. But in this case, the member function FindItem has to communicate success or failure to the caller somehow.

Of course, we could use a return value of true or false to indicate whether the UPC is found, but we're already using the return value to return the StockItem to the calling function. We could add a reference argument to the FindItem function and use it to set the value of a variable in the caller's code, but that's very nonintuitive; functions that don't modify their arguments are easier to use and less likely to cause surprises.

Using a Null Object

There's one more possibility. We can return a null object of the StockItem class; that is, an object that exists solely to serve as a placeholder, representing the desired object that we couldn't find.

I like this solution, because when the member function terminates, the application program has to test something anyway to see if the desired StockItem was found; why not test whether the returned object is a null StockItem? This solution, while quite simple, requires a minor change to our implementation of StockItem: we have to add an IsNull member function to our StockItem class so that we can tell whether the returned StockItem is a null StockItem or a "normal" one. We have to add the line bool IsNull(); to the class interface and provide the implementation as shown in Figure 6.26. I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are item5.h, item5.cpp, and itemtst5.cpp, respectively.

Figure 6.26. The implementation of IsNull (from codeitem5.cpp)
bool StockItem::IsNull()
{
   if (m_UPC == "")
       return true;

   return false;
}

As you can see, not much rocket science is involved in this member function: all we do is check whether the UPC in the item is the null string "". If it is, we return true; otherwise, we return false. Since no real item can have a UPC of "", this should work well. Let's hear from Susan on the topic of this function (and function return values in general).

Susan: This is something I have not thought about before: When you call a function where does the return value go?

Steve: Wherever you put it. If you say x = sum(weight);, then the return value goes into x. If you just say sum(weight);, then it is discarded.

Susan: Why is it discarded?

Steve: Because you didn't use it; therefore, the compiler assumes you have no further use for it.

Susan: So the return value can be used in only one place?

Steve: Yes, unless you save it in a variable, in which case you can use it however you like.

Figure 6.27 shows the implementation of FindItem, which uses CheckUPC to check whether the requested UPC is the one in the current item and returns a null StockItem if the desired UPC isn't found in the inventory list.

Figure 6.27. FindItem function for Inventory class (from codeinvent1.cpp)
StockItem Inventory::FindItem(string UPC)
{
  short i;
  bool Found = false;

  for (i = 0; i < m_StockCount; i ++)
     {
     if (m_Stock[i].CheckUPC(UPC))
         {
         Found = true;
         break;
         }
     }

  if (Found)
      return m_Stock[i];

  return StockItem();
}

This function illustrates a new way to specify the condition of an if statement: if a bool variable is specified as the condition, the if will be true if the bool is true, and otherwise the if will be false. Thus, the expression if (Found) will execute its controlled statement if the value of Found is true, but not if the value of Found is false. This is equivalent in effect to writing if (Found == true), but is less error-prone because you can't make the mistake of writing = (assignment) rather than == (testing for equality). Therefore, it's best to test bool conditions by this implicit method, even though the syntax is a little more cryptic.

Here's my interchange with Susan on CheckUPC:

Susan: About the first if statement in this CheckUPC function, if (m_Stock[i].CheckUPC(UPC)): does that mean if you find the UPC you are looking for then the program breaks and you don't need to continue looking? In that case, what does the statement Found = true; do? Are you setting Found to the value true?

Steve: That's right. If we've actually found the item we're looking for, then Found will have been set to true, so we'll return the real item; otherwise, we'll return a null StockItem to indicate that we couldn't find the one requested.

After we get a copy of the correct StockItem and update its inventory via DeductSaleFromInventory, we're not quite done; we still have to update the "real" StockItem in the Inventory object. This is the task of the last function in our Inventory class: UpdateItem. Figure 6.28 shows its implementation.

Figure 6.28. UpdateItem function for the Inventory class (from codeinvent1.cpp)
bool Inventory::UpdateItem(StockItem Item)
{
  string UPC = Item.GetUPC();

  short i;
  bool Found = false;

  for (i = 0; i < m_StockCount; i ++)
     {
     if (m_Stock[i].CheckUPC(UPC))
         {
         Found = true;
         break;
         }
     }

  if (Found)
      m_Stock[i] = Item;

  return Found;
}

Why do we need this function? Because we are no longer operating on the "real" StockItem, as we had been when we accessed the inventory Vec directly in the previous version of the application program. Instead, we are getting a copy of the StockItem from the Inventory object and changing that copy. Thus, to have the final result put back into the Inventory object, we need to use the UpdateItem member function of Inventory, which overwrites the original StockItem with our changed version.

This function needs another function in the StockItem class to get the UPC from a StockItem object, so that UpdateItem can tell which object in the m_Stock Vec is the one that needs to be updated. This additional function, GetUPC, is shown in Figure 6.29.

Figure 6.29. The implementation of GetUPC (from codeitem5.cpp)
string StockItem::GetUPC()
{
   return m_UPC;
}

The application program also needs one more function, GetPrice(), to be added to the interface of StockItem to retrieve the price from the object once we have found it. This is shown in Figure 6.30.

Figure 6.30. The implementation of GetPrice (from codeitem5.cpp)
short StockItem::GetPrice()
{
  return m_Price;
}

We're almost ready to examine the revised test program. First, though, let's pause for another look at all of the interfaces and implementations of the StockItem and Inventory classes. The interface for the Inventory class is in Figure 6.31.

Figure 6.31. Current interface for Inventory class (codeinvent1.h)
class Inventory
{
public:
  Inventory();

  short LoadInventory(std::ifstream& is);
  StockItem FindItem(std::string UPC);
  bool UpdateItem(StockItem Item);

private:
  Vec<StockItem> m_Stock;
  short m_StockCount;
};

Figure 6.32 contains the implementation for Inventory.

Figure 6.32. Current implementation for Inventory class (codeinvent1.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "Vec.h"
#include "item5.h"
#include "invent1.h"
using namespace std;

Inventory::Inventory()
: m_Stock (Vec<StockItem>(100)),
  m_StockCount(0)
{
}

short Inventory::LoadInventory(ifstream& is)
{
  short i;
  for (i = 0; i < 100; i ++)
     {
     m_Stock[i].Read(is);
     if (is.fail() != 0)
         break;
     }

  m_StockCount = i;
  return m_StockCount;
}

StockItem Inventory::FindItem(string UPC)
{
  short i;
  bool Found = false;

  for (i = 0; i < m_StockCount; i ++)
     {
     if (m_Stock[i].CheckUPC(UPC))
         {
         Found = true;
         break;
         }
     }

  if (Found)
      return m_Stock[i];

  return StockItem();
}

bool Inventory::UpdateItem(StockItem Item)
{
  string UPC = Item.GetUPC();

  short i;
  bool Found = false;

  for (i = 0; i < m_StockCount; i ++)
     {
     if (m_Stock[i].CheckUPC(UPC))
         {
         Found = true;
         break;
         }
     }

  if (Found)
      m_Stock[i] = Item;

  return Found;
}

Figure 6.33 shows the interface for StockItem.

Figure 6.33. Current interface for StockItem class (codeitem5.h)
class StockItem
{
public:
  StockItem();

  StockItem(std::string Name, short InStock, short Price,
  std::string Distributor, std::string UPC);

  void Display();
  void Read(std::istream& is);

  bool CheckUPC(std::string ItemUPC);
  void DeductSaleFromInventory(short QuantitySold);
  short GetInventory();
  std::string GetName();
  bool IsNull();
  short GetPrice();
  std::string GetUPC();

private:
  short m_InStock;
  short m_Price;
  std::string m_Name;
  std::string m_Distributor;
  std::string m_UPC;
};

The implementation for StockItem is in Figure 6.34.

Figure 6.34. Current implementation for StockItem class (codeitem5.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "item5.h"
using namespace std;

StockItem::StockItem()
: m_InStock(0), m_Price(0), m_Name(),
  m_Distributor(), m_UPC()
{
}

StockItem::StockItem(string Name, short InStock,
short Price, string Distributor, string UPC)
: m_InStock(InStock), m_Price(Price), m_Name(Name),
  m_Distributor(Distributor), m_UPC(UPC)
{
}

void StockItem::Display()
{
  cout << "Name: ";
  cout << m_Name << endl;
  cout << "Number in stock: ";
  cout << m_InStock << endl;
  cout << "Price: ";
  cout << m_Price << endl;
  cout << "Distributor: ";
  cout << m_Distributor << endl;
  cout << "UPC: ";
  cout << m_UPC << endl;
  cout << endl;
}

void StockItem::Read(istream& is)
{
 getline(is,m_Name);
 is >> m_InStock;
 is >> m_Price;
 is.ignore();
 getline(is,m_Distributor);
 getline(is,m_UPC);
}

bool StockItem::CheckUPC(string ItemUPC)
{
  if (m_UPC == ItemUPC)
      return true;

  return false;
}

void StockItem::DeductSaleFromInventory(short QuantitySold)
{
  m_InStock -= QuantitySold;
}

short StockItem::GetInventory()
{
  return m_InStock;
}

string StockItem::GetName()
{
  return m_Name;
}


bool StockItem::IsNull()
{
  if (m_UPC == "")
     return true;

  return false;
}

short StockItem::GetPrice()
{
  return m_Price;
}

string StockItem::GetUPC()
{
  return m_UPC;
}

To finish this stage of the inventory control project, Figure 6.35 is the revised test program that uses the Inventory class rather than doing its own search through a Vec of StockItems. This program can perform either of two operations, depending on what the user requests. Once the UPC has been typed in, the user is prompted to type either "C" for price check or "S" for sale. Then an if statement selects which of the two operations to perform. The code for the S (i.e., sale) operation is the same as it was in the previous version of this application, except that, of course, at that time it was the only possible operation so it wasn't controlled by an if statement. The code for the C (i.e., price check) operation is new, but it's very simple. It merely displays both the item name and the price.

Figure 6.35. Updated inventory application (codeitemtst5.cpp)
#include <iostream>
#include <fstream>
#include <string>
#include "vec.h"
#include "item5.h"
#include "invent1.h"
using namespace std;

int main()
{
   ifstream InputStream("shop2.in");
   string PurchaseUPC;
   short PurchaseCount;
   string ItemName;
   short OldInventory;
   short NewInventory;
   Inventory MyInventory;
   StockItem FoundItem;
   string TransactionCode;

   MyInventory.LoadInventory(InputStream);

   cout << "What is the UPC of the item? ";
   cin >> PurchaseUPC;

   FoundItem = MyInventory.FindItem(PurchaseUPC);
   if (FoundItem.IsNull())
       {
       cout << "Can't find that item. Please check UPC." << endl;
       return 0;
       }

   OldInventory = FoundItem.GetInventory();
   ItemName = FoundItem.GetName();

   cout << "There are currently " << OldInventory << " units of "
   << ItemName << " in stock." << endl;

   cout << "Please enter transaction code as follows:
";
   cout << "S (sale), C (price check): ";
   cin >> TransactionCode;

   if (TransactionCode == "C" || TransactionCode == "c")
       {
       cout << "The name of that item is: " << ItemName << endl;
       cout << "Its price is: " << FoundItem.GetPrice();
      }
   else if (TransactionCode == "S" || TransactionCode == "s")
      {
      cout << "How many items were sold? ";
      cin >> PurchaseCount;

      FoundItem.DeductSaleFromInventory(PurchaseCount);
      MyInventory.UpdateItem(FoundItem);

      cout << "The inventory has been updated." << endl;

      FoundItem = MyInventory.FindItem(PurchaseUPC);
      NewInventory = FoundItem.GetInventory();

      cout << "There are now " << NewInventory << " units of "
      << ItemName << " in stock." << endl;
      }

   return 0;
}

The only part of the program that might not be obvious at this point is the expression in the if statement that determines whether the user wants to enter a price check or sale transaction. The first part of the test is if (TransactionCode == "C" || TransactionCode == "c"). The || is the "logical or" operator. An approximate translation of this expression is "if at least one of the two expressions on its right or left is true, then produce the result true; if they're both false, then produce the result false".[21] In this case, this means that the if statement will be true if the TransactionCode variable is either C or c. Why do we have to check for either a lower- or upper-case letter, when the instructions to the user clearly state that the choices are C or S?

[21] The reason it's only an approximate translation is that there is a special rule in C++ governing the execution of the || operator: if the expression on the left is true, then the expression on the right is not executed at all. The reason for this short-circuit evaluation rule is that in some cases you may want to write a right-hand expression that will only be legal if the left-hand expression is false.

This is good practice because users generally consider upper and lower case letters to be equivalent. Of course as programmers, we know that the characters c and C are completely different; however, we should humor the users in this harmless delusion. After all, they're our customers!

Susan had a couple of questions about this program.

Susan: What do the following output statements mean: cout << S (sale); and cout << C (price check);? I am not clear as to what they are doing.

Steve: Nothing special; the prompts S (sale) and C (price check) are just to notify the user what his or her choices are.

Susan: OK, so the line with the || is how you tell the computer to recognize upper case as well as lower case to have the same meaning?

Steve: Yes, that's how we're using that operator here.

Susan: So what do you call those || thingys?

Steve: They're called "vertical bars". The operator that is spelled || is called a "logical OR" operator, because it results in the value true if either the left-hand or the right-hand expression is true (or if both are true).

Susan: What do you mean by using else and if in the line else if (TransactionCode == "S" || TransactionCode == "s")? I don't believe I have seen them used together before.

Steve: I think you're right. Actually, it's not that mysterious. As always, the else means that we're specifying actions to be taken if the original if isn't true. The second if merely checks whether another condition is true and executes its controlled block if so.

Assuming that you've installed the software from the CD in the back of this book, you can try out this program. First, you have to compile it by following the compilation instructions on the CD. Then type itemtst5 to run the program. When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines". When the program asks you for a transaction code, type S for "sale" or P for "price check", and then hit ENTER.

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

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