TOC PREV NEXT INDEX


I've just released a deduplicating backup product for
VMWare Workstation and Server.
Do you need to reduce the storage needed to maintain multiple backups?
Or do you need multiple snapshots on VMWare Server?
If you have either of these needs, just click
here to get started for only $29 (limited time offer ends July 31st, 2011)!

C++: A Dialog


CHAPTER 6 Taking Inventory
Now we have enough of the fundamentals of programming under our belts to look at some of the more powerful features of C++. As I've mentioned before, C++ was intended as the successor to C. What I haven't told you is exactly why it was invented.

To understand this, we'll have to consider the differences between two basic kinds of variables that exist in both C and C++: native (i.e., defined in the language itself) and user-defined (i.e., defined by the programmer). The native types that we've been using are char, short, and unsigned short (and int, but only for the return type of main), all of which have been inherited from C.1 The user-defined types we've been using are the string, Vec, and the types of cin and cout.

What difference does it make whether a variable is native or user-defined? Quite a bit of difference, in fact. In both C and C++, variables of the native types are fully supported by the language. To be "fully supported" means that variables can be defined, initialized, assigned values, passed as arguments and return values, and compared to other values of the same type. Such a variable can be assigned storage in either the static or auto storage classes: If a variable is auto, the storage is assigned at entry to the function where it is defined, and released automatically at exit from that function; if it is static, it is initialized to some reasonable value either at link time (for a global variable) or upon the first entry to the function where it is defined (for a local variable).

However, most of these facilities aren't available to user-defined data types in C. For example, variables of such types can't be compared; this limitation results from the fact that the C compiler has no idea how to compare two variables of a type that you define. Similarly, what is a reasonable default value for a variable of a user-defined type? Presumably, the user (i.e., the programmer) knows, but the C compiler doesn't.

In this chapter, we'll see how to give a C++ compiler enough information to allow data types that we define to behave almost exactly like the native types. Susan had a few questions on this topic:

Susan: I think I need to find out something here. I am getting the impression that what is "native" is C and what is "user-defined" is C++. Is that right? And if so, why?
Steve: Pretty much so. As to why, the answer is pretty simple: the reason that C++ was invented in the first place was to add good support for user-defined types to the efficiency of C.
Susan: Okay, but why are cin and cout user-defined? We didn't define them. They're from the standard library, right? Then shouldn't they be native?
Steve: That's a good point, but in fact things defined in the standard library aren't native. Here's a quick way to tell whether a variable type is native or user-defined: if you don't need to #include a header file to use it, then it's native. Anything not native is user-defined.
6.1. Definitions
A class is a user-defined type.2
A class interface tells the compiler what facilities the class provides. This interface is usually found in a header file, which by convention has the extension .h.3
A class implementation tells the compiler how to implement the facilities defined in the class interface. This is usually found in a source code file, which in the case of the compiler on the CD in the back of this book usually has the extension .cpp.
An object is a variable of a class type. Its behavior is defined by the header file and implementation of the class.4
A member function is a function that is part of the definition of a class.
A member variable is a variable that is part of the definition of a class.
Object-oriented programming is the organization of programs as collections of objects exhibiting user-defined behavior, rather than as collections of functions operating on variables of native data types.5
Encapsulation is the concept of hiding the details of a class inside the implementation of that class, rather than exposing them in the interface. This is one of the primary organizing principles that characterize object-oriented programming.
Internals, in the case of native data types, refers to details of the implementation of these types in the compiler. In the case of class types, internals means the details of implementation of the type rather than what it does for the user.

6.2. Objectives of This Chapter

By the end of this chapter, you should
1. Understand what a user-defined type (a class) is, and how it is defined.
2. Understand how variables of some simple classes are created, destroyed, and copied.
3. Understand how and why access to the internals of a class is controlled.

6.3. User-defined Data Types

In C++, a user-defined variable is called an object. Each object has a type, just like variables of native types (short, char, etc.). For example, if we define a class called StockItem (as we will do in this chapter), then an object can be of type StockItem, just as a native variable can be of type short. However, an additional step is required when we want to use user-defined types. Since the compiler has no intrinsic knowledge of these types, we have to tell it exactly what they are and how they work. We do this by defining a class, which specifies both the data contained in the user-defined variable and what operations can be performed on these data.

Here's how Susan reacted upon her first encounter with this idea.

Susan: I can tell that there is only one thing that I think that I understand about this. That is, that C++ is not a language. You have to make it up as you go along. . . .
That may be overdoing it a bit, but there is a grain of truth in her observation: C++ is more of a "language kit" than it is a language. What do I mean by this?

I mean that to use C++ in the most effective way, rather than merely as a "better C", it is necessary to create data types and tell the compiler how to treat them as though they were native data types. So far in this book, we have been using data types that were previously defined, either by the compiler and language (native types, e.g., short, char) or by libraries (class types, e.g., string). Now we're going to actually make up our own types that will be usable just like native types. The difference between using variables and making up new variable types is analogous to the difference between using a program and writing a program, but carried to the next higher level.

In the event that you find this notion hard to understand, you're not alone; so did Susan.

Susan: This is an outrage! I didn't understand one other word after this as I was far beyond anything that could even be described as shock. I think I did faint. I may as well have been in a coma.
Interestingly enough, she actually did understand this idea of making up our own data types, so perhaps she was overestimating the degree of her shock.

Before we get back to the technical explanation of how we create new data types, I'm sure one more question is burning in your mind: Why should we do this? What's wrong with the native types like char and short? The answer is simple: we make up types so that we can match the language to the needs of the problem we're trying to solve. For example, suppose we want to write a program to do inventory control for a small business like a grocery store. Such a program needs objects representing items in the store, which have prices, names, and so on. We need to define each of these types of objects so that it can display the behavior appropriate to the thing it represents. The availability of objects that have relevance to the problem being solved makes it much easier to write (and read) a program to handle inventory than if everything has to be made of shorts and chars.

I suspect that the advantages of making up one's own data types may still not be apparent to you, so let me make an analogy with natural languages. Making up new data types in C++ is in some ways quite similar to making up new words in English (for example). You might think that if everyone made up new words, the result would be chaos. Actually, this is correct, with the very important exception of technical jargon and other vocabularies that are shared by people who have more in common than simply being speakers of English. For example, physicians have their own "language" in the form of medical terminology. Of course, a cynical observer might conclude that the reason for such specialized vocabulary is to befuddle or impress the naive listener, and of course it can be used for that purpose. However, there is also a much more significant and valid reason for using technical jargon: to make it possible for experts in a field to communicate with one another quickly and precisely. The same is true of creating our own data types; they enable us to write programs that are more understandable to those who are conversant with the problems being solved. It's much easier to talk to a store owner about inventory objects than about shorts and chars!

Here's the discussion that Susan and I had on this topic:

Susan: Why should we have user-defined data types?
Steve: So that you can match the language to the needs of the problem you're trying to solve. For example, if you were writing a nurse's station program in C++, you would want to have objects that represented nurses, doctors, patients, various sorts of equipment, and so on. Each of these objects would display the behavior appropriate to the thing or person it was representing.
Susan: Why do you need that? What if each individual who spoke English made up a unique version of English (well, it is user-defined, right?), how could we communicate? This is garbage.
Steve: We need user-defined types for the same reason that specialists need jargon in their technical fields. For example, why do you health-care professionals need words like tachycardia? Why don't you just say "a fast heartbeat" in simple English?
Hey, that's not a bad way to explain this: Adding classes is like adding specialized vocabulary to English. I don't remember ever seeing that explanation before; what do you think of it?
Susan: Huh? Then you are saying that, by defining a class of objects, they can take on more realistic qualities than just abstract notions? That is, if I wanted to represent nurses in a program, then I would do it with a class named nurse and then I can define in that program the activities and functions that the nurse objects would be doing. Is this how you keep everything straight, and not mix them up with other objects?
Steve: Yes, that's one of the main benefits of object-oriented programming. You might be surprised how hard it is to teach an old-line C programmer the importance of this point.
Susan: So is this what object-oriented programming is? I have heard of it, but never knew what it meant. Could it also be described as user-defined programming? I guess there are advantages to teaching a novice; you don't have to undo old ideas to make way for newer ones. So, anything that is user-defined is a class? That is, native variables are not classes?
Steve: Right. Every user-defined type is a class; data items of a class type are called objects. Variables of native types are not objects in the object-oriented sense.
Susan: OK, so if I want to make up something, then what I make up is called a class as opposed to the other type of stuff that isn't made up and is really part of C++; that is called native. That is intuitive, thank you. Then the class is made up of data items? And what about native variables; are they objects? I guess just the variables of the class are called objects because I just read your definition for object. So native variables are not objects, they are just variables. Am I am talking in circles again?
Steve: No, you're not; you're making perfect sense. The only point you have missed is that there are functions in the objects, as well as data items. We'll get into that shortly.
Susan: So Steve, tell me: What have I been doing up to this point? How does this new stuff compare to the old stuff and which one is it that the programmer really uses? (Let's see, do I want curtain 1 or 3; which one holds the prize?) I just want to get a little sense of direction here; I don't think that is a whole lot to ask, do you?
Steve: What you've been doing up to this point is using classes (string, Vec) as well as native types like short and char. This new stuff shows how to create classes like string, rather than just using them.6
Assuming that I've sold you on the advantages of making up our own data types, let's see how we can actually do it. Each data type is represented by a class, whose full definition is composed of two parts: the interface definition (usually contained in a file with the extension .h), and the implementation definition (usually contained in a file with the extension .cpp). The interface definition tells the compiler (and the class user) what the class does, while the implementation definition tells the compiler how the objects of that class actually perform the functions specified in the interface definition. Let's take a look at a step-by-step description of how to create and use a class.
1. Write the class interface definition, which will be stored in a file with the extension .h. In our example of a StockItem class, we'll use item1.h to hold our first version of this interface definition. This definition tells the compiler the names and types of the member functions and member variables that make up the objects of the class, which gives the compiler enough information to create objects of this class in a user's program.
2. Write the class implementation definition, which will be stored in a file with the extension .cpp; in our example, the first one of these will be stored in the file item1.cpp. This definition is the code that tells the compiler how to perform the operations that the interface definition refers to. The implementation definition file must #include the interface definition file (item1.h, in this case) so that the compiler has access to the interface that is being implemented. It needs this information to check that the implementation is proper (i.e., that it conforms to the interface specified in the interface definition file).
3. Write the program that uses objects in the class to do some work. The first such program we'll write will be itemtst1.cpp. This program also needs to #include the interface definition file, so that the compiler can tell how to create objects of this class.
4. Compile the class implementation definition to produce an object file (item1.o). This makes the class available to the user program.
5. Compile the user program to produce an object file (itemtst1.o).
6. Link the object file from the user program, the object file from the class implementation definition, and any necessary libraries together to form a finished executable. Our first sample will be called itemtst1.exe.
A couple of items in this list need some more discussion. Let's see how Susan brought them to my attention.
Susan: I have a problem here. First under item 2, you put "The class implementation definition file must #include"; excuse me, but that doesn't make sense. What do you mean by #include? How do you say that, "pound include"?
Steve: Yes, that's how it's pronounced. You could also leave off the "pound" and just say "include", and every C and C++ programmer would understand you. As you may recall, a #include statement causes the compiler to pretend that the code in the included file was typed in instead of the #include statement.
Susan: Section 6 that stuff with the linking. . .isn't that done by the compiler; if not, how do you do it?
Steve: The linker does it, but the compiler is generally capable of calling the linker automatically for you; that's why we haven't needed to worry about this before.
Susan: OK, where is the linker? Is it not part of the compiler software? If not, where does it come from?
Steve: Every compiler comes with one, but you can also buy one separately if you prefer.
Susan: Who puts it in your computer? Also, how do you "call" the linker if you have always had the compiler do it for you?
Steve: It is installed along with the compiler. You can tell the compiler not to call it automatically if you prefer to do it manually; there are reasons to do that sometimes. For example, when you're making a change that affects only one module in a large program, you can recompile only that one module, then relink all the object files again to make the executable.
Susan: How do you do that?
Steve: It varies according to what compiler you're using. With the compiler on the CD in the back of the book, you specify source files and object files in the command line that you use to run the compiler. The compiler will compile the source files and then link them with the object files.

6.4. The StockItem class

Now let's start on our first class definition, which is designed to help solve the problem of maintaining inventory in a small grocery store. We need to keep track of all the items that we carry, so we're going to define a class called StockItem. The StockItem class, like other classes, is composed of a number of functions and variables. As I suggested earlier, to make this more concrete, think of something like Lego blocks, which you can put together to make parts that can in turn be used to build bigger structures. The smallest Legos are the native types, and the bigger, composite ones are class types.

For the compiler to be able to define an object correctly, we'll have to tell it the names and types of the member variables that will be used to store the information about each StockItem; otherwise, it wouldn't know how much memory to allocate for a StockItem object.

To figure out what member variables a StockItem needs, we must consider what information it will require to keep track of its corresponding item in the stock of the store. After some thought, I've come up with the following list of member variables:

1. The name of the item (m_Name),
2. The number in stock (m_InStock),
3. The distributor that we purchase it from (m_Distributor),
4. The price we charge (m_Price), and
5. The item number, or UPC (m_UPC).
What I mean by "item" is actually something like "chunky chicken soup, 16 oz.", rather than a specific object like a particular can of soup. In other words, every can of soup with the same item number is considered equivalent to every other can of soup with the same item number, so all we have to keep track of for each item can be described by the above data. For the item number, we'll use the Universal Product Code (UPC), which is printed as a bar code on almost every product other than fresh produce; it's a 10-digit number, which we'll represent as a string for convenience.

Susan took me to task about the notion of a StockItem object vs. a specific object like a particular can of soup:

Susan: When you say "rather than a specific object", how much more specific can you get than "chunky chicken soup, 16 oz."?
Steve: Each can of chunky chicken soup is at least slightly different from every other one; at least they are in different places.
Let's recap what we know about a StockItem so far. We need a member variable in the StockItem class definition for each value in the above description: the name of the item (m_Name), its price (m_Price), the number of items in stock (m_InStock), the name of the distributor (m_Distributor), and the UPC (m_UPC) of the item. Of course, merely storing these data isn't very useful unless we can do something with them. Therefore, objects of the StockItem class also need to be able to perform several operations on their data; we'll start by giving them the ability to display their contents. Figure 6.1 illustrates a very simple way that this class might be used.
FIGURE 6.1. The initial sample program for the StockItem class (code\itemtst1.cpp)
#include <iostream>
#include <string>
#include "item1.h"
using namespace std;

int main()
{
StockItem soup;

soup = StockItem("Chunky Chicken",32,129,
"Bob's Distribution","123456789");

soup.Display();

return 0;
}

This program defines a StockItem named soup with no initial data specified, tells it to assign itself some data, asks it to display itself via a function called Display, and finally terminates normally. By the time we're done with this chapter, you'll understand exactly how every operation in this program is performed by the StockItem class. Before we get too deeply into this particular class, however, we should look at the functions that almost all classes have in common. First, let's define some more terms we'll need for the discussion.

6.5. More Definitions

A concrete data type is a class whose objects behave like variables of native data types. That is, the class gives the compiler enough information that its objects can be created, copied, assigned, and automatically destroyed just as native variables are. The StockItem class that we will construct in this chapter is a concrete data type.
A constructor is a member function that creates new variables of the class to which it belongs. All constructors have the same name as the class for which they are constructors; therefore, the constructors for StockItem variables also have the name StockItem.
A default constructor is a constructor that is used when no initial value is specified for an object. Because it is a constructor, it has the same name as the class; since it is used when no initial value is specified, it has no arguments. Thus, StockItem() is the default constructor for the StockItem class.
A copy constructor is a constructor that makes a new object with the same contents as an existing object of the same type.
An assignment operator is a member function that sets a pre-existing object to the same value as another object of the same type.
A destructor is a member function that cleans up when an object expires; for a local object, this occurs at the end of the block where that object is defined.
The object member access operator is "." (period). It separates an object name, on its left, from the member variable or member function on its right.
::, when used immediately after a class name, is the class membership operator, which indicates the class a variable or function belongs to.7

6.6. Concrete Data Types

While different classes vary considerably in the facilities that they provide, there are significant benefits to a class whose objects behave like those of native types. As I've just mentioned, such a class is called a concrete data type. To make a class a concrete data type, we must define certain member functions that allow creation, copying, and deletion to behave as with a native variable.

Susan wanted to see a chart illustrating the correspondence between what the compiler does for a native type and what we have to do to make a type a concrete data type. Of course, I complied with her request (see Figure 6.2).

Because the member functions listed in that chart are so fundamental to the proper operation of a class, the compiler will generate a version of each of them for us if we don't write them ourselves, just as the corresponding behavior is automatically supplied for the native types. As we will see in Chapter 7, the compiler-generated functions are generally too simplistic to be used in a complex class. In such a case we need to create our own versions of these functions and I'll illustrate how to do that at the appropriate time. However, with a simple class such as the one we're creating here, the compiler-generated versions of the assignment operator, copy constructor, and destructor are perfectly adequate, so we won't be creating our own versions of these functions for StockItem.

FIGURE 6.2.
The Native Problem A Concrete Plan
Here are the essential facilities that the compiler provides for every native type:

To make a concrete data type, we have to provide each of these facilities for our new type. By no coincidence, there is a specific type of member function to provide each of them. Here are the official names and descriptions of each of these four functions:

1. The ability to create a variable with no specified initial value, e.g., short x;.

1. A default constructor that can create an object when there is no initial value specified for the object, e.g., "StockItem x;"

2. The ability to pass a variable as an argument to a function; in this case, the compiler has to make a copy of the variable so that the called function doesn't change the value of the variable in the calling function.

2. A copy constructor that can make a new object with the same contents as an existing object of the same type, to allow arguments of that type to be passed by value.

3. The ability to assign a value of an appropriate type to an existing variable such as x = 22; or x = z;.

3. An assignment operator that is used to set an existing object to the value of another object of the same type, e.g., "x = y;".

4. Reclaiming the storage assigned to a variable when it ceases to exist, so that those memory addresses can be reallocated to other uses. In the case of auto variables, this is at the end of the block where they were created; with static variables, it's at the end of execution of the program.
4. A destructor that cleans up when an object ceases to exist, including releasing the memory that the object has occupied. For an auto object, this occurs at the end of the block where the object was created; with static variables, it's at the end of execution of the program.

Comparison between native and user-defined types

Susan was a bit confused about the distinction between the compiler-generated versions of these essential functions and the compiler's built-in knowledge of the native types:

Susan: Aren't the compiler-generated versions the same thing as the native versions?
Steve: No, they're analogous but not the same. The compiler-generated functions are created only for objects, not for native types. The behavior of the native types is implemented directly in the compiler, not by means of functions.
Susan: I'm confused. Maybe it would help if you explained what you mean by "implemented directly in the compiler". Are you just saying that objects are implemented only by functions, whereas the native types are implemented by the built-in facilities of the compiler?
Steve: You're not confused, you're correct.
Susan: OK, here we go again. About the assignment operator, what is this "version"? I thought you said earlier that if you don't write your own assignment operator it will use the native operator. So I don't get this.
Steve: There is no native assignment operator for any class type; instead, the compiler will generate an assignment operator for a class if we don't do it ourselves.
Susan: Then how can the compiler create an assignment operator if it doesn't know what it is doing?
Steve: All the compiler-generated assignment operator does is to copy all of the members of the right-hand variable to the left-hand variable. This is good enough with the StockItem class. We'll see in Chapter 7 why this isn't always acceptable.
Susan: Isn't a simple copy all that the native assignment operator does?
Steve: The only native assignment operators that exist are for native types. Once we define our own types, the compiler has to generate assignment operators for us if we don't do it ourselves; otherwise, it would be impossible to copy the value of one variable of a class type to another without writing an assignment operator explicitly.
Susan: OK, this is what confused me, I just thought that the native functions would be used as a default if we didn't define our own in the class type, even though they would not work well.
Steve: There aren't any native functions that work on user-defined types. That's why the compiler has to generate them when necessary. But I think we have a semantic problem here, not a substantive one.
Susan: Why doesn't it default to the native assignment operator if it doesn't have any other information to direct it to make a class type operator? This is most distressing to me.
Steve: There isn't any native assignment operator for a StockItem. How could there be? The compiler has never heard of a StockItem until we define that class.
Susan: So it would be a third type of assignment operator. At this point, I am aware of the native type, the user-defined type and a compiler-generated type.
Steve: Right. The native type is built into the compiler, the user-defined type is defined by the user, and the compiler-generated type is created by the compiler for user-defined types where the user didn't define his own.
Susan: Then the native and the compiler-generated assignment operator are the same? If so, why did you agree with me that there must be three different types of assignment operators? In that case there would really only be two.
Steve: No, there is a difference. Here is the rundown:
1. (Native assignment) The knowledge of how to assign values of every native type is built into the compiler; whenever such an assignment is needed, the compiler emits prewritten code that copies the value from the source variable to the destination variable.
2. (Compiler-generated assignment) The knowledge of how to create a default assignment operator for any class type is built into the compiler; if we don't define an assignment operator for a given class, the compiler generates code for an assignment operator that merely copies all of the members of the source variable to the destination variable. Note that this is slightly different from 1, where the compiler copies canned instructions directly into the object file whenever the assignment is done; here, it generates an assignment operator for the specific class in question and then uses that operator whenever an assignment is done.
3. (User-defined assignment) This does exactly what we define it to do.
Susan: Did you ever discuss the source variable and the destination variable? I don't recall that concept in past discussions. I like this. All I remember is when you said that = means to set the variable on the left to the value on the right. Does this mean that the variable on the left is the destination variable and the value on the right is the source variable?
Steve: Yes, if the value on the right is a variable; it could also be an expression such as "x + 2".
Susan: But how could it be a variable if it is a known value?
Steve: It's not its value that is known, but its name. Its value can vary at run time, depending on how the program has executed up till this point.
Susan: So the main difference is that in 1 the instructions are already there to be used. In 2 the instructions for the assignment operator have to be generated before they can be used.
Steve: That's a good explanation.
After my explanation of the advantages of a concrete data type, Susan became completely convinced, so much so that she wondered why we would ever want anything else.
Susan: On your definition for concrete data types. . . this is fine, but what I am thinking is that if something wasn't a concrete data type, then it wouldn't work, that is unless it was native. So what would a workable alternative to a concrete data type be?
Steve: Usually, we do want our objects to be concrete data types. However, there are times when, say, we don't want to copy a given object. For example, in the case of an object representing a window on the screen, copying such an object might cause another window to be displayed, which is probably not what we would want to happen.
Susan: OK, so what would you call an object that isn't of a concrete data type?
Steve: There's no special name for an object that isn't of a concrete data type.
Susan: So things that are not of a concrete data type have no names?
Steve: No, they have names; I was just saying that there's no term like non-concrete data type, meaning one that doesn't act like a native variable. There is a term abstract data type, but that means something else.
Susan: See, this is where I am still not clear. Again, if something is not a concrete data type, then what is it?
Steve: What is the term for a person who is not a programmer? There isn't any special term for such a person. Similarly, there's no special term for a class that doesn't act like a native variable type. If something isn't a concrete data type, then you can't treat it like a native variable. Either you can't copy it, or you can't assign to it, or you can't construct it by default, or it doesn't go away automatically at the end of the function where it is defined (or some combination of these). The lack of any of those features prevents a class from being a concrete data type.
Susan: Of what use would it be to have a class of a non-concrete data type? To me, it just sounds like an error.
Steve: Sometimes it does make sense. For example, you might want to create a class that has no default constructor; to create an element of such a class, you would have to supply one or more arguments. This is useful in preventing the use of an object that doesn't have any meaningful content; however, the lack of a default constructor does restrict the applicability of such a class, so it's best to provide such a constructor if possible.
Before we can implement the member functions for our StockItem class, we have to define what a StockItem is in more detail than my previous sketch.8 Let's start with the initial version of the interface specification for that class (Figure 6.3), which includes the specification of the default constructor, the Display function, and another constructor that is specific to the StockItem class.

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 item1.h, item1.cpp, and itemtst1.cpp, respectively.

FIGURE 6.3. The initial interface of the StockItem class (code\item1.h)
class StockItem
{
public:
StockItem();

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

void Display();

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

Your first reaction is probably something like "What a bunch of malarkey!" Let's take it a little at a time, and you'll see that this seeming gibberish actually has a rhyme and reason to it. First we have the line class StockItem. This tells the compiler that what follows is the definition of a class interface, which as we have already seen is a description of the operations that can be performed on objects of a given user-defined type; in this case, the type is StockItem. So that the compiler knows where this description begins and ends, it is enclosed in {}, just like any other block of information that is to be treated as one item.9

After the opening {, the next line says public:. This is a new type of declaration called an access specifier, which tells the compiler the "security classification" of the item(s) following it, up to the next access specifier. This particular access specifier, public, means that any part of the program, regardless of whether it is defined in this class, can use the items starting immediately after the public declaration and continuing until there is another access specifier. In the current case, all of the items following the public specifier are operations that we wish to perform on StockItem objects. Since they are public, we can use them anywhere in our programs. You may be wondering why everything isn't public; why should we prevent ourselves (or users of our classes) from using everything in the classes? It's not just hardheartedness; it's actually a way of improving the reliability and flexibility of our software, as I'll explain later.

As you might imagine, this notion of access specifiers didn't get past Susan without a serious discussion. Here's the play-by-play account.

Susan: So, is public a word that is used often or is it just something you made up for this example?
Steve: It's a keyword of the C++ language, which has intrinsic meaning to the compiler. In this context, it means "any function, inside or outside this class, can access the following stuff, up to the next access specifier (if any)". Because it is a keyword, you can't have a variable named public, just as you can't have one named if.
Susan: These access specifiers: What are they, anyway? Are they always used in classes?
Steve: Yes.
Susan: Why aren't they needed for native variables?
Steve: Because you can't affect the implementation of native types; their internals are all predefined in the compiler.
Susan: What does internals mean? Do you mean stuff that is done by the compiler rather than stuff that can be done by the programmer?
Steve: Yes, in the case of native data types. In the case of class types, internals means the details of implementation of the type rather than what it does for the user.
Susan: You know, I understand what you are saying about internals; that is, I know what the words mean, but I just can't picture what you are doing when you say implementation. I don't see what is actually happening at this point.
Steve: The implementation of a class is the code that is responsible for actually doing the things that the interface says the objects of the class can do. All of the code in the item1.cpp file is part of the implementation of StockItem. In addition, the private member variables in the header file are logically part of the implementation, since the user of the class can't access them directly.
Susan: Why is a class function called a member function? I like class function better; it is more intuitive.
Steve: Sorry, I didn't make up the terminology. However, I think member function is actually more descriptive, because these functions are members (parts) of the objects of the class.
Susan: So on these variables, that m_ stuff; do you just do that to differentiate them from a native variable? If so, why would there be a confusion, since you have already told the compiler you are defining a class? Therefore, all that is in that class should already be understood to be in the class rather than the native language. I don't like to look at that m_ stuff; it's too cryptic.
Steve: It's true that the compiler can tell whether a variable is a member variable or a global variable. However, it can still be useful to give a different name to a member variable so that the programmer can tell which is which. Remember, a member variable looks like a global variable in a class implementation, because you don't declare it as you would an argument or a local variable.
Now we're up to the line that says StockItem();. This is the declaration for a function called a constructor, which tells the compiler what to do when we define a variable of a user-defined type. This particular constructor is the default constructor for the StockItem class. It's called the "default" constructor because it is used when no initial value is specified by the user; the empty parentheses after the name of the function indicate the lack of arguments to the function. The name of the function is the clue that it's a constructor. The name of a constructor is always the same as the name of the class for which it's a constructor, to make it easier for the compiler to identify constructors among all of the possible functions in a class.

This idea of having variables and functions "inside" objects wasn't intuitively obvious to Susan:

Susan: Now, where you talk about mixing a string and a short in the same function, can this not be done in the native language?
Steve: It's not in the same function but in the same variable. We are creating a user-defined variable that can be used just like a native variable.
Susan: OK, so you have a class StockItem. And it has a function called StockItem. But a StockItem is a variable, so in this respect a function can be inside a variable?
Steve: Correct. A StockItem is a variable that is composed of a number of functions and other variables.
Susan: OK, I think I am seeing the big picture now. But you know that this seems like such a departure from what I thought was going on before, where we used native types in functions rather than the other way around. Like when I wrote my little program, it would have shorts in it but they would be in the function main. So this is a complete turnabout from the way I used to think about them; this is hard.
Steve: Yes, that is a difficult transition to make. Interestingly enough, experience isn't necessarily an advantage here; you haven't had as much trouble with it as some professional programmers who have a lot more experience in writing functions as "stand-alone" things with no intrinsic ties to data structures. However, it is one of the essentials in object-oriented programming; most functions live "inside" objects and do the bidding of those objects, rather than being wild and free.
Why do we need to write our own default constructor? Well, although we have already specified the member variables used by the class so that the compiler can assign storage as with any other static or auto variable, that isn't enough information for the compiler to know how to initialize the objects of the class correctly.10 Unlike a native variable, the compiler can't set a newly created StockItem to a reasonable value, since it doesn't understand what the member variables of a StockItem are used for. That is, it can't do the initialization without help from us. In the code for our default constructor, we will initialize the member variables to legitimate values so that we don't have to worry about having an uninitialized StockItem lying around as we did with a short in a previous example. Figure 6.4 shows what the code to our first default constructor looks like.
FIGURE 6.4. The default constructor for the StockItem class (from code\item1.cpp)

StockItem::StockItem()

: m_Name(), m_InStock(0), m_Price(0), m_Distributor(), m_UPC()

{

}

Let's use this example of a StockItem class to illuminate the distinction between interface and implementation. As I've already mentioned, the implementation of a class is the code that is responsible for actually doing the things promised by the interface of that class. The interface was laid out in Figure 6.3. With the exception of the different versions of the test program that illustrates the use of the StockItem class, all of the code that we will examine in this chapter is part of the implementation. This includes the constructors and the Display member function.

So you can keep track of where this fits into the "big picture", the code in Figure 6.4 is the implementation of the function StockItem::StockItem() (i.e., the default constructor for the class StockItem), whose interface was defined in Figure 6.3. Now, how does it work? Actually, this function isn't all that different from a "regular" function, but there are some important differences. First of all, the name looks sort of funny: Why is StockItem repeated?

The answer is that, unlike "regular" (technically, global) functions, a member function always belongs to a particular class. That is, such a function has special access to the data and other functions in the class, and vice versa. To mark its membership, its name consists of the name of the class (in this case, StockItem), followed by the class membership operator ::, followed by the name of the function (which in this case, is also StockItem); as we have already seen, the name of a constructor is always the same as the name of its class. Figure 6.5 shows how each component of the function declaration contributes to the whole.

FIGURE 6.5. Declaring the default constructor for the StockItem class

This function belongs to the StockItem class


It is a constructor, because its name is the same as the name of the class







and it has no arguments
(i.e., it is the default constructor for its class)








StockItem ::

StockItem

( )

If you've really been paying attention, there's one thing that you may have noticed about this declaration as compared with the original declaration of this function in the class interface definition for StockItem (Figure 6.3). In that figure, we declared this same function as StockItem();, without the additional StockItem:: on the front.11 Why didn't we need to use the StockItem:: class membership notation in the class interface definition? Because inside the declaration of a class, we don't have to specify what class the member functions belong to; by definition, they belong to the class we're defining. Thus, StockItem() in the class interface declaration means "the member function StockItem, having no arguments"; i.e., the default constructor for the StockItem class.

Susan didn't have any trouble with this point, which was quite a relief to me:

Susan: Oh, so you don't have to write StockItem::StockItem in the interface definition because it is implied by the class StockItem declaration?
Steve: Right.
Now let's look at the part of the constructor that initializes the member variables of the StockItem class, the member initialization list. The start of a member initialization list is signified by a : after the closing ")" of the constructor declaration, and the expressions in the list are separated by commas. A member initialization list can be used only with constructors, not any other type of functions.

The member initialization list of the default StockItem constructor is: : m_InStock(0), m_Price(0), m_Name(), m_Distributor(), m_UPC(). What does this mean exactly? Well, as its name indicates, it is a list of member initialization expressions, each of which initializes one member variable. In the case of a member variable of a native type such as short, a member initialization expression is equivalent to creating the variable with the initial value specified in the parentheses. In the case of a member variable of a class type, a member initialization expression is equivalent to creating the variable by calling the constructor that matches the type(s) of argument(s) specified in the parentheses, or the default constructor if there are no arguments specified. So the expression m_InStock(0) is equivalent to the creation and simultaneous initialization of a local variable by the statement short m_InStock = 0;. Similarly, the expression m_Name() is equivalent to the creation and simultaneous initialization of a local variable by the statement string m_Name;. Such a statement, of course, would initialize the string m_Name to the default value for a string, which happens to be the empty C string literal "".

Using a member initialization list is the best way to set up member variables in a constructor, for two reasons. First, it's more efficient than using assignment statements to set the values of member variables. For example, suppose that we were to write this constructor as shown in Figure 6.6.

FIGURE 6.6. Another way to write the default StockItem constructor
StockItem::StockItem()
{
m_InStock = 0;
m_Price = 0;
m_Name = "";
m_Distributor = "";
m_UPC = "";
}
If we wrote the constructor that way, before we got to the opening "{" of the constructor, all of the member variables that had constructors (here, the strings) would be initialized to their default values. After the "{", they would be set to the values we specified in the code for the constructor. It's true that we could solve this problem in this specific example by simply not initializing the strings at all, as that would mean that they would be initialized to their default values anyway; but that solution wouldn't apply in other constructors such as the one in Figure 6.7, where the member variables have specified values rather than default ones.

The second reason that we should use a member initialization list to initialize our member variables is that some member "variables" aren't variables at all but constants. We'll see how to define consts, as they are called in C++, in a later chapter. For now, it's enough to know that you can't assign a value to a const, but you can (and indeed have to) initialize it; therefore, when dealing with member consts, a member initialization list isn't just a good idea, it's the law.

There is one fine point that isn't obvious from looking at the code for this constructor: The expressions in a member initialization list are executed in the order in which the member variables being initialized are declared in the class definition, which is not necessarily the order in which the expressions appear in the list. In our example, since m_InStock appears before m_Name in the class definition, the member initialization expression for m_InStock will be executed before the expression initializing m_Name. This doesn't matter right now, but it will be important in Chapter 7, where we will be using initialization expressions whose order of execution is important.

You may have noticed that the body of the function (the part inside the {}) shown in Figure 6.4 is empty, because all of the work has already been done by the member initialization list. This is fairly common when writing constructors, but not universal; as we'll see in Chapter 7, sometimes a constructor has to do something other than initialize member variables, in which case we need some code inside the {}.

Susan objected to my cavalier use of the empty C string literal "":

Susan: Excuse me, but what kind of value is " " ? Do you know how annoying it is to keep working with nothing?
Steve: It's not " ", but "". The former has a space between the quotes and the latter does not; the former is a one-character C string literal consisting of one space, while the latter is a zero-character C string literal.
Susan: OK, so "" is an empty C string literal, but could you please explain how this works?
Steve: The "" means that we have a C string literal with no data in it. The compiler generates a C string literal consisting of just the terminating null byte.
Susan: What good does that do? I don't get it.
Steve: Well, a string has to have some value for its char* to point to; if we don't have any real data, then using an empty C string literal for that purpose is analogous to setting a numeric value to 0.
Susan: OK, so this is only setting the strings in the default constructor to a value that the compiler can understand so you don't get an error message, although there is no real data. We're trying to fool the compiler, right?
Steve: Close, but not quite. Basically, we want to make sure that we know the state of the strings in a default StockItem. We don't want to have trouble with uninitialized variables; remember how much trouble they can cause?
Susan: Yes, I remember. So this is just the way to initialize a string when you don't know what real value it will end up having?
Steve: Yes, that's how we're using it here.
Now let's get back to the member variables of StockItem. One important characteristic of any variable is its scope, so we should pay attention to the scope of these variables. In Chapter 5, we saw two scopes in which a variable could be defined: local (i.e., available only within the block where it was defined) and global (i.e., available anywhere in the program). Well, these variables aren't arguments (which have local scope) since they don't appear in the function's header. On the other hand, they aren't defined in the function; therefore, they aren't local variables. Surely they can't be global variables, after I showed you how treacherous those can be.

6.7. The class Scope

I haven't misled you on that point; there is another scope called class scope, which applies to all member variables of a class. Variables with class scope occupy separate memory locations for each object; i.e., each object has its own separate set of member variables distinct from the member variables of any other objects.12 In the case of StockItem, this set of member variables consists of m_InStock, m_Price, m_Name, m_Distributor, and m_UPC. Member functions of a class can access member variables of objects of that class without defining them, as though they were global variables.

In addition to scope, each member variable has another attribute we have already encountered: an access specifier. The access of nonmember functions to any member variable or member function depends on the access specifier in effect when the member variable or function was declared. If you look back at Figure 6.3, you'll see that the line private: precedes the declaration of the member variables in the StockItem class. The keyword private is an access specifier, like public; however, where a public access specifier allows any function to access the items that follow it, a private access specifier allows only member functions to access items that follow it.

Susan had some more questions about access specifiers, including this new one, private:

Susan: It seems to me that the access specifiers act more like scope than anything. Are they about the same?
Steve: Yes, the difference between public and private is somewhat analogous to the difference between global and local variables, but the latter distinction affects where a variable is stored and when it is initialized, whereas an access specifier controls what functions can access the variable. However, because member variables are defined inside classes, they can't be global, nor can they be local in the sense that a "regular" (i.e., nonmember) variable can be; a member variable must always live inside a single occurrence of an object of its class.
But although scope rules and access specifiers are similar in some ways in that they affect where a variable can be used, they aren't exactly the same. Scope defines where a variable is visible, whereas access specifiers control where a variable (or function) is accessible. That is, if you write a program that tries to read or modify a private variable from outside the class implementation, the compiler knows what you're trying to do but won't let you do it. On the other hand, if you try to access a local variable from a function where it isn't defined, the compiler just tells you it never heard of that variable, which indeed it hasn't in that context. For example, let's suppose that the local variable x defined in function abc has no existence in any other function; in that case, if you try to access a variable named x in another function, say def, where it hasn't been defined, you'll get an error message from the compiler telling you that there is no variable x in function def. However, if there is a private member variable called x defined in class ghi, and you try to access that member variable from a nonmember function, the compiler will tell you that you're trying to do something illegal. It knows which x you mean, but it won't let you access it because you don't have permission.
Susan: Are they necessary for every class?
Steve: Pretty much. The default specifier for a class is private; that is, everything you declare in a class interface before the first explicit access specifier is private. Of course, this also means that if you don't ever provide an explicit access specifier in a given class, then everything declared in that class will be private. This isn't usually very useful, because without any public functions it's hard to use a class at all.
Susan: There is a default? Then why would the default be the least useful?
Steve: To make the programmer specify what should be publicly accessible rather than have it happen automatically. In general, it's best to keep as much as possible private, to reduce the dependency of external code on the internal implementation of the class. This makes it easier to change that implementation without causing trouble for the users of the class.
Susan: OK, that makes sense now. Are there any other kinds of access specifiers or are these the only two?
Steve: Actually, there's one more called protected, that is sort of in between public and private; we'll start using it in Chapter 9.
Susan also wanted some more details about this new class scope.
Susan: How about explaining the difference between a class scope and a public access specifier?
Steve: Variables declared in a class, regardless of their access specifier, have class scope; that means that they live as long as the object that contains them. The access specifier determines who can access these variables, but does not affect their lifetime.
Of course, the constructor StockItem::StockItem(), by virtue of being a member function, has access to all member variables, so the private access specifier doesn't apply to it. We'll see later how that access specifier comes into play.

Now that we know what kind of variables the StockItem::StockItem() function deals with, its behavior isn't very mysterious: it simply initializes the member variables to 0 or the default string value (""), whichever is appropriate to their types. That's all very well, but it doesn't answer a very important question: What exactly do these member variables do? The answer is that they don't do anything by themselves; rather, they are the "raw material" the member functions use to implement the behavior that we want a StockItem to display. If you recall the discussion of interface vs. implementation, then you'll appreciate that the private member variables are also essentially part of the implementation and not part of the interface: even though they are defined in the header file, the user of the class can't access them directly.

That's why we call variables that are declared inside the class definition member variables and functions that are declared inside the class definition member functions; they "belong" to the class that we're defining. The member functions set, change, and use the values of the member variables in the course of implementing the behaviors that the StockItem class interface definition promises.13

Susan wasn't buying all this malarkey about member variables without some further explanation. Here's how the discussion went:

Susan: What do m_InStock and m_Price and the others actually do? It seems we are missing a verb here.
Steve: They don't do anything by themselves. They are the member variables used to store the count and price of the goods described by a StockItem object, respectively. In the default constructor, they are both set to 0, indicating a StockItem with no content. This is the equivalent of the value 0 that is used to initialize statically allocated numeric variables and is used in the same way; that is, any StockItem that is created without a value is set to this empty state. Notice that this doesn't apply only to statically allocated StockItems, but to all StockItems; this is an example where a user-defined type is superior to a native type. That is, we don't have to worry about having an uninitialized StockItem, because the default constructor ensures that every StockItem is set to a known value when it is created.
Susan: Ugh. Don't remind me about uninitialized variables. Okay, that makes more sense now.
So much for the "high-altitude" description of what a class does. Now let's get back to the details that make it work, starting with a little puzzle: figuring out where the StockItem::StockItem() function is used in the test program in Figure 6.1 on page 307. Believe it or not, this constructor is actually used in that program; to be exact, the line StockItem soup; calls it. Remember that the basic idea of constructing a class is to add data types to the language that aren't available "out of the box". One of the functions that we have to help the compiler with is initialization; a main purpose for the StockItem::StockItem() constructor is to initialize variables of the StockItem type that aren't explicitly initialized. That's why it's called a default constructor.

Susan didn't immediately cotton to the idea of calling a default constructor by simply defining a variable of that class.

Susan: Sure, defining an object is simple if you don't lose your mind defining the classes first.
Steve: It is simple for the application programmer (the user of the class). We're doing the hard part so he can just use the objects without having to worry about any of this stuff.
Susan: Huh? Isn't the "user of the class" always the same as the "writer of the class"?
Steve: Not necessarily. You've been using strings (and Vecs, for that matter) for some time now without having to be concerned about how they work. This is not unusual.
Susan: Yeah, but if you are a programmer you will be a class writer, not just a user.
Steve: Probably not with respect to all classes. You may very well write your own application-specific classes but use existing ones for all of the low-level stuff like Vecs, strings, etc.
That's one reason why we separate interfaces and implementations: so that not everyone who uses our classes has to know exactly how they are implemented.
You should generally write a default constructor for every class you define, to guarantee the state of any "default constructed" variable. If you don't declare a default constructor, the compiler will supply one for you; however, since it doesn't know much about your class, it won't be able to guarantee very much about the initial state of one of your variables. In fact, all the native types will be left in a random state, as though they were declared but not initialized; this is an undesirable condition, as we've already seen in another context. The moral is that you should define your own default constructor. As you can see from our example, it's not much work.

So why did I say "generally", rather than "always"? Because there are some times when you don't want to allow an object to be created unless the "real" data for it are available. As with the copy constructor, the compiler will generate a default constructor for you automatically if you don't declare one yourself. If you want to make it impossible to create an object via that compiler-generated default constructor, you can declare a private default constructor that will cause a compiler error in any user code that tries to define an object of that class without specifying an initial value. You don't have to implement this constructor, because a program that tries to use it won't compile.

Susan thought that the idea of having to define a default constructor for each class was a bit off the wall.

Susan: When you say that "you should define one of these (default constructors) for every class you define..." my question is how? What are you talking about? I thought a default meant just that, it was a default, you don't have to do anything with it, it is set to a preassigned value.
Steve: It's true that the class user doesn't have to do anything with the default constructors. However, the class writer (that's us, in this case) has to define the default constructor so that when the class user defines an object without initializing it, the new object has a reasonable state for an "empty" object. This prevents problems like those caused by uninitialized variables of native types.

6.8. More about the StockItem class Interface

Now let's continue with our analysis of the class interface for the StockItem class (Figure 6.3 on page 316). Before we can do anything with a StockItem object, we have to enter the inventory data for that object. This means that we need another constructor that actually sets the values into a StockItem. We also need some way to display the data for a StockItem on the screen, which means writing a Display function.

The next line of that figure is the declaration of the "normal" constructor that creates an object with actual data:

StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);
We can tell that this function is a constructor because its name, StockItem, is the same as the name of the class. One interesting thing about this constructor is that its name is the same as the previous constructor: namely, StockItem. This is a very handy facility, called function overloading, which isn't limited to constructors. The combination of the function name and argument types is called the signature of a function; two functions that have the same name but differ in the type of at least one argument are distinct functions.14 In the case of the default constructor, there are no arguments, so that constructor is used where no initial data are specified for the object. The statement StockItem soup; in the sample program (Figure 6.1) fits that description, so the default constructor is used. However, in the next statement of the sample program, we have the expression:
StockItem("Chunky Chicken", 32, 129, "Bob's Distribution", "123456789");
This is clearly a call to a constructor, because the name of the function is the name of a class, StockItem. Therefore, the compiler looks for a constructor that can handle the set of arguments in this call and finds the following declaration:
StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);
The first, fourth, and fifth arguments to the constructor are strings, while the second and third are shorts. Since these types all match those specified in the expression in the sample program, the compiler can translate that expression into a call to this constructor.

Figure 6.7 shows the code for that constructor.

FIGURE 6.7. Another constructor for the StockItem class (from code\item1.cpp)
StockItem::StockItem(string Name, short InStock,
short Price, string Distributor, string UPC)
: m_Name(Name), m_InStock(InStock), m_Price(Price),
m_Distributor(Distributor), m_UPC(UPC)
{
}

As you can see, nothing about this constructor is terribly complex; it merely uses the member initialization list to set the member variables of the object being constructed to the values of their corresponding arguments.

Referring to Standard Library Identifiers in Header Files

Before we get to the implementation of this function, there's one thing in this header file that we haven't seen before in real code, although I've mentioned it in passing: the use of the standard library namespace specifier std as it is used in the declaration:
StockItem(std::string Name, short InStock, short Price, std::string Distributor, std::string UPC);
What does std::string mean? It notifies the compiler that the name string refers to a name from the standard library. Until this point, whenever we needed to refer to identifiers from the standard library, we have either given the compiler blanket permission to import all the names from the standard library into the current namespace that we are using, or to import specific names from the standard library into the current namespace. To give the compiler permission to import all the names in the standard library we use the statement using namespace std; and an example of blanket namespace permission for a specific identifier is using std::cout;. These two ways of telling the compiler what we mean seem to cover all of the possibilities and are much more convenient than having to write std:: many times. So why do we need to use this seemingly redundant method of specifying the namespace of something from the standard library?

Because it's a very bad idea to have any blanket namespace permissions in a header file. That can change the behavior of an implementation file without the knowledge of the person who wrote that implementation file. Let's say, for example, that someone has made up their own string class and therefore doesn't want to tell the compiler to import the identifier string from the standard library. If we wrote the line using std::string; in a header file that he included in his implementation file, the compiler would import the identifier string from the standard library, which would cause confusion between his string and the standard library version. So the only sensible thing to do is to avoid using statements in header files.

But sometimes, we do need to refer to standard library names in our header files. In that event, we just attach std:: to the beginning of the names of standard library identifiers and then the compiler knows exactly what we mean.

The Need for More Than One Constructor

Now that that's cleared up, you may be wondering why we need more than one constructor for the StockItem class. Susan had that same question, and I had some answers for her.

But why do we need more than one constructor? Susan had that same question, and I had some answers for her.

Susan: How many constructors do you need to say the same thing?
Steve: They don't say exactly the same thing. It's true that every constructor in the StockItem class makes a StockItem; however, each argument list varies. The default constructor makes an empty StockItem and therefore doesn't need any arguments, whereas the constructor StockItem::StockItem(string Name, short InStock, short Price, string Distributor, string UPC) makes a StockItem with the values specified by the Name, InStock, Price, Distributor, and UPC arguments in the constructor call.
Susan: Are you saying that in defining a class you can have two functions that have the same name, but they are different in only their arguments and that makes them unique?
Steve: Exactly. This is the language feature called function overloading.
Susan: So StockItem soup; is the default constructor in case you need something that can create uninitialized objects?
Steve: Not quite; the default constructor for the StockItem class is StockItem::StockItem(), which doesn't need any arguments, because it constructs an empty StockItem. The line StockItem soup; causes the default constructor to be called to create an empty StockItem named soup.
Susan: And the line StockItem("Chunky Chicken",32,129,"Bob's Distribution","123456789"); is a constructor that finally gets around to telling us what we are trying to accomplish here?
Steve: Again, not quite. That line causes a StockItem with the specified contents to be created, by calling the constructor StockItem::StockItem (string Name, short InStock, short Price, string Distributor, string UPC);.
Susan: So are you saying that for every new StockItem you have to have a new constructor for it?
Steve: No, there's one constructor for each way that we can construct a StockItem. One for situations where we don't have any initial data (the default constructor), one for those where we're copying one StockItem to another (the compiler-generated copy constructor), and one for situations where we are supplying the data for a StockItem. There could be other ones too, but those are all we have right now.
Once that expression has been translated, the compiler has to figure out how to assign the result of the expression to the StockItem object called soup, as requested in the whole statement:
soup = StockItem("Chunky Chicken", 32, 129, "Bob's Distribution", "123456789");
Since the compiler has generated its own version of the assignment operator = for the StockItem class, it can translate that part of the statement as well. The result of the entire statement is that the StockItem object named soup has the value produced by the constructor.

Finally, we have the line soup.Display(); which asks soup to display itself on the screen. Figure 6.8 shows the code for that function.

FIGURE 6.8. Display member function for the StockItem class (from code\item1.cpp)

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;

}

This is also not very complicated; it just uses << to copy each of the parts of the StockItem object to cout, along with some identifying information that makes it easier to figure out what the values represent.

Susan wanted to know how we could use << without defining a special version for this class.

Susan: Hey, how come you don't have to define << as a class operator? Does the compiler just use the native <<? And that works OK?
Steve: We're using << only for types that the standard library already knows about, which includes all of the native types as well as its string class. If we wanted to apply << to a StockItem itself, we'd have to write our own version; you'll see how we do that when we go into our implementation of string in Chapter 7.
Susan: Then please explain to me why << is being used in Figure 6.8, which is for the StockItem class.
Steve: It's being used for strings and shorts, not objects of the StockItem class. The fact that the strings and shorts are inside the StockItem class is irrelevant in this context; they're still strings and shorts, and therefore can be displayed by the << operators that handle strings and shorts.
Susan: So the stuff you get out of the standard library is only for the use of class types? Not native?
Steve: No. The iostream part of the standard library is designed to be able to handle both native types and class types; however, the latter use requires the class writer to do some extra work, as we'll see when we add these functions to our version of the string class in Chapter 8.
Susan: So that means that the library is set up to understand things we make up for our class types?
Steve: Sort of. As long as we follow the library's rules when creating our own versions of << and >>, we'll be able to use those operators to read and write our data as though the ability to handle those types was built into the library.
That should clear up most of the potential problems with the meaning of this Display function. However, it does contain one construct that we haven't seen before: void. This is the return type of the Display function, as might be apparent from its position immediately before the class name StockItem. But what sort of return value is a void? In this context, it means simply that this function doesn't supply a return value at all.

You won't be surprised to learn that Susan had a few questions about this idea of functions that return no value.

Susan: How can a function not return a value? Then what is the point? Then would it "have no function"?
Steve: Now you're telling programming jokes? Seriously, though, the point of calling a function that returns no value is that it causes something to happen. The Display function is one example; it causes the value of a StockItem object to be displayed on the screen. Another example is a "storage function"; calling such a function can cause it to modify the value of some piece of data it is maintaining so when you call the corresponding "retrieval function", you'll get back the value the "storage function" put away. Such lasting effects of a function call (other than returning a value) are called side effects.
Susan: But even a side effect is a change, so then it does do something after all, right?
Steve: Sure, it does something; every function should do something, or you wouldn't write (or call) it. However, some functions don't return any value to the calling program, in which case we specify their return type as void.
That takes care of the public part of the class definition. Now what about the private part? As I mentioned before in the discussion of how a class is defined, the access specifier private means that only member functions of the class can access the items after that specifier. It's almost always a good idea to mark all the member variables in a class as private, for two reasons.
1. If we know that only member functions of a class can change the values of member data, then we know where to look if the values of the data are incorrect. This can be extremely useful when debugging a program.
2. Marking member variables as private simplifies the task of changing or deleting those member variables should that become necessary. If the member variables are public, then we have no idea what functions are relying on their values. That means that changing or deleting these member variables can cause havoc anywhere in the system. Allowing access only by member functions means that we can make changes freely as long as all of the member functions are kept up to date.
Both of these advantages of keeping member variables private can be summed up in the term encapsulation, which means "hiding the details inside the class implementation rather than exposing them in the interface". This is one of the primary organizing principles that characterizes object-oriented programming.

Now that we've covered all of the member functions and variables of the StockItem class, Figure 6.9 shows the interface for the StockItem class again. As noted previously, the test program for this class, itemtst1.cpp, is shown in Figure 6.1.

FIGURE 6.9. The initial interface of the StockItem class (code\item1.h)
class StockItem
{
public:
StockItem();

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

void Display();

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

There's only one more point about the member variables in the StockItem class that needs clarification; surely the price of an object in the store should be in dollars and cents, and yet we have only a short to represent it. As you know by now, a short can hold only a whole number, from -32768 to 32767. What's going on here?

Only that I've decided to store the price in cents rather than dollars and cents. That is, when someone types in a price I'll assume that it's in cents, so "246" would mean 246 cents or $2.46. This would of course not be acceptable in a real program, but for now it's not a problem.

This "trick" allows prices up to $327.67 (as well as negative numbers for things like coupons), which should be acceptable for our hypothetical grocery store. In Chapter 9, I'll give you some tips on using a different kind of numeric variable that can hold a greater variety of values. For now, though, let's stick with the short.

Figure 6.10 shows the implementation for the StockItem class.

FIGURE 6.10. The initial implementation of the StockItem class (code\item1.cpp)
#include <iostream>
#include <string>
#include "item1.h"
using namespace std;

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

StockItem::StockItem(string Name, short InStock,
short Price, string Distributor, string UPC)
: m_Name(Name), m_InStock(InStock), m_Price(Price),
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;
}
Susan had a few questions about the way that we specified the header files in this program:
Susan: How come the #include "item1.h" are in "" instead of <>? I thought all header files are in <>?
Steve: Putting an include file name in "" tells the compiler to start looking for the file in the current directory and then search the "standard places". Using the <> tells the compiler to skip the current directory and start with the "standard places"; that is, the places where the standard library header files are stored. What the "standard places" are depends on the compiler.
Susan: I know I have not been the most focused test reader in the last week, but did I miss something here? Did you explain way back in an earlier chapter and I just forgot? I want to make sure that you have explained this in the book, and even if you had earlier it would not be a bad idea to remind the reader about this.
Steve: Yes, as a matter of fact I did mention it briefly, in a footnote on page 164, but I'm not surprised that you don't recall reading it. Actually, you've been quite focused, although not in quite the same area.
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 itemtst1 to run the program. You'll see that it indeed prints out the information in the StockItem object. You can also run it under the debugger by following the usual instructions for that method.

That's good as far as it goes, but how do we use this class to keep track of all of the items in the store? Surely we aren't going to have a separately named StockItem variable for each one.

Handling a Number of StockItems with a Vec

This is another application for our old friend the Vec; specifically, we need a Vec of StockItems to hold the data for all the StockItems in the store. In a real application we would need to be able to vary the number of elements in the Vec, unlike our previous use of Vecs. After all, the number of items in a store can vary from time to time.

As it happens, the size of a Vec can be changed after it is created. However, in our example program we'll ignore this complication and just use a Vec that can hold 100 StockItems.15 Even with this limitation, we will have to keep track of the number of items that are in use, so that we can store each new StockItem in its own Vec element and keep track of how many items we may have to search through to find a particular StockItem. Finally, we need something to read the data for each StockItem from the inventory file where it's stored when we're not running the program.

Susan had some questions about these details of the test program:

Susan: In the paragraph when you are talking about the number of items, I am a little confused. That is, do you mean the number of different products that the store carries or the quantity of an individual item available in the store at any given time?
Steve: The number of different products, which is the same as the number of StockItems. Remember, each StockItem represents any number of objects of that exact description.
Susan: So what you're referring to is basically all the inventory in the store at any given period of time?
Steve: Exactly.
Susan: What do you mean by "need something to read the data" and "where it's stored when we're not running the program"? I don't know what you are talking about, I don't know where that place would be.
Steve: Well, where are the data when we're not running the program? The disk. Therefore, we have to be able to read the information for the StockItems from the disk when we start the program.
Susan: Okay, that makes sense now.
Steve: I'm glad to hear it.
Figure 6.11 is a little program that shows the code necessary to read the data for the StockItem Vec into memory when the program starts up.
FIGURE 6.11. Reading and displaying a Vec of StockItems (code\itemtst2.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;

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

InventoryCount = i;

for (i = 0; i < InventoryCount; i ++)
{
AllItems[i].Display();
}

return 0;
}

This program has a number of new features that need examination. First, we've had to add the "file stream" header file <fstream> to the list of include files, so that we will be able to read data in from a file. The way we do this is to create an ifstream object that is "attached" to a file when the object is constructed. In this case, the line ifstream ShopInfo("shop2.in"); creates an ifstream object called ShopInfo, and connects it to the file named shop2.in.

The first line in the upper loop is AllItems[i].Read(ShopInfo);, which calls the function Read for the ith StockItem in the AllItems Vec, passing the ShopInfo ifstream object to a new StockItem member function called Read, which uses ShopInfo to get data from the file and store it into its StockItem (i.e., the ith element in the Vec).

This whole process was anything but obvious to Susan.

Susan: How does just adding the header file fstream enable you to read data in from a file?
Steve: The file fstream contains the declaration of the ifstream class.
Susan: Where did the ifstream class come from?
Steve: It's part of the standard library.
Susan: Where did it come from and how did it get there? Who defined it and when was it written? Your only reference to this is just "The way we do this is to create an ifstream object that is `attached' to a file when the object is constructed". If this is just something that you wrote to aid this program that we don't have to worry about at this time, then please mention this.
Steve: I didn't write it, but we don't have to worry about it very much. I'll explain it to the minimum extent necessary.
After that bit of comic relief, let's get back to reality. Figure 6.12 is the implementation of the new Read function.
FIGURE 6.12. The Read function for the StockItem class (from code\item2.cpp)

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);

}

The task this function performs is fairly simple, but there are some complexities in its implementation due to peculiarities of the standard library. Let's start with the first line of the body of the function:

getline(s,m_Name);

What does this do? It gets a line of data from the input stream, whose name is s, and puts it into our m_Name variable. Why can't we use the >> operator for this purpose, as we indeed do for the next two member variables that we are reading, m_Instock and m_Price?

6.9. Working around the Standard Library

We can't use the >> operator because the implementation of that operator in the C++ standard library stops reading data into a string variable whenever it sees a blank or other "white space" character, rather than only at the end of a line. Therefore, if we tried to use >> to read a line of data such as "3-ounce cups" into m_Name, we would only get the part of it before the first blank. The rest of the data would be left on the input stream for the next read operation to retrieve.

Personally, I think it would be much better for >> to read the whole line into a string and as we'll see, that's how I did it when I implemented my own string class. But, as Bjarne has said, "Victories over the type system of a language are always Pyrrhic victories". Even though the string class and the rest of the standard library aren't quite an intrinsic part of the C++ language in the sense that the native types are, they are woven tightly enough into the fabric of the language that we should use them unless we have a compelling reason not to do so. In this case, the cure (throwing out the standard library string class) is worse than the disease, so we'll just have to put up with the peculiarities of the standard library even when they make our programs somewhat more complicated.

The next two lines, which read the data from the input stream into m_Instock and m_Price, respectively, aren't anything special; we've seen how we can use >> to read data into numeric variables before. But what does that next line, s.ignore();, do?

This is yet another of the peculiarities of the standard library. You see, when we read data into a numeric variable via the >> operator, that operator stops taking characters from the input stream as soon as it sees a character that doesn't belong in a number, like a space, newline (`\n'), or alphabetic character. In this case, that character is the newline at the end of the line containing the price. That seems reasonable enough in itself.

However, once it sees that character it doesn't remove it from the input stream, but leaves it there for the next input operator. Therefore, if we didn't take account of this odd behavior, our next input statement, getline(s,m_Distributor);, would see the `\n' as the first character in the input stream and would stop reading at that point. That would leave our m_Distributor variable empty.

In order to get around this problem, we have to use the ignore function to ignore the next character in the input stream after we finish reading the price. This allows the value of the m_Distributor variable to be read correctly from the input stream.

The Second Version of the StockItem Interface

Now that we're finished with that unfortunate complication in our function, there's just one more construct here we haven't seen before: the & in istream&. As soon as we take a look at the new interface to the StockItem class (Figure 6.13), we'll see exactly what that means. 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 item2.h, item2.cpp, and itemtst2.cpp, respectively.
FIGURE 6.13. The second version of the interface for the StockItem class (code\item2.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);

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

The &, as used here in the context of declaring an argument to a function, means that the argument to which it refers is a reference argument, rather than a "normal" (value) argument. It's important to understand this concept thoroughly, so let's go into it in detail.

6.10. 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 code\item2.h
This function doesn't return anything












It belongs to the StockItem class












Its name is Read;












and its argument is a reference to an istream











void

StockItem ::

Read

( istream& s)

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
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 345) 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

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
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 345) 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 (code\itemtst3.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.

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 (code\item4.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 code\item4.cpp)

bool StockItem::CheckUPC(string ItemUPC)

{

if (m_UPC == ItemUPC)

return true;

else

return false;

}

FIGURE 6.19. StockItem::DeductSaleFromInventory (from code\item4.cpp)
void StockItem::DeductSaleFromInventory(short QuantitySold)
{
m_InStock -= QuantitySold;
}

FIGURE 6.20. StockItem::GetInventory (from code\item4.cpp)
short StockItem::GetInventory()
{
return m_InStock;
}

FIGURE 6.21. StockItem::GetName (from code\item4.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 (code\itemtst4.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 360. 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 (code\invent1.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
FIGURE 6.24. Default constructor for Inventory class (from code\invent1.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 code\invent1.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 code\item5.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 code\invent1.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 code\invent1.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 code\item5.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 code\item5.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 (code\invent1.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 (code\invent1.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 (code\item5.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 (code\item5.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 (code\itemtst5.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:\n";
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?

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.

6.11. Checking Inventory for a Misplaced Item

By this point, you very understandably might have gotten the notion that we have to make changes to our classes every time we need to do anything slightly different in our application program. In that case, where's the advantage of using classes instead of just writing the whole program in terms of shorts, chars, and so on?

Well, this is your lucky day. It just so happens that the next (and last) scenario we are going to examine requires no more member functions at all; in fact, we don't even have to change the application program. Here it is, for reference: "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".

The reason we don't have to do anything special for this scenario is that we're already displaying the name and inventory for the item as soon as we find it. Of course, if we hadn't already handled this issue, there are many other ways that we could solve this same problem. For example, we could use the Display member function of StockItem to display an item as soon as the UPC lookup succeeds, rather than waiting for the user to indicate what operation our application is supposed to perform.

For that matter, we'd have to consider a number of other factors in writing a real application program, even one that does such a simple task as this one. For example, what would happen if the user indicated that 200 units of a particular item had been sold when only 100 were in stock? Also, how would we find an item if the UPC isn't available? The item might very well be in inventory somewhere, but the current implementation of Inventory doesn't allow for the possibility of looking up an item by information other than the UPC.

Although these topics and many others are essential to the work of a professional programmer, they would take us too far afield from our purpose here. We'll get into some similar issues later, when we discuss the topic of "software engineering" in Chapter 12.

Now let's review what we've covered in this chapter.

6.12. Review

The most important concept in this chapter is the idea of creating user-defined data types. In C++, this is done by defining a class for each such data type. Each class has both a class interface, which describes the behavior that the class displays to the "outside world" (i.e., other, unrelated functions), and a class implementation, which tells the compiler how to perform the behaviors promised in the interface definition. A variable of a class type is called an object. With proper attention to the interface and the implementation of a class, it is possible to make objects behave just like native variables; that is, they can be initialized, assigned, compared, passed as function arguments, and returned as function return values.

Both the interface and the implementation of a class are described in terms of the functions and variables of which the class is composed. These are called member functions and member variables, because they belong to the class rather than being "free-floating" or localized to one function like the global functions and local variables we encountered earlier.

Of course, one obvious question is why we need to make up our own variable types. What's wrong with char, short, and the rest of the native types built into C++? The answer is that it's easier to write an inventory control program, for example, if we have data types representing items in the stock of a store, rather than having to express everything in terms of the native types. An analogy is the universal preference of professionals to use technical jargon rather than "plain English". Jargon conveys more information, more precisely, in less time.

Creating our own types of variables allows us to use objects rather than functions as the fundamental building blocks of our programs, which is the basis of the "object-oriented programming" paradigm.

Then we examined how creating classes differs from using classes, which we have been doing throughout the book. A fairly good analogy is that creating your own classes is to using classes as writing a program is to using a program.

Next, we went through the steps needed to actually create a new class; our example is the StockItem class, which is designed to simulate tracking of inventory for a small grocery store. These steps include writing the interface definition, writing the implementation, writing the program that uses the class, compiling the implementation, compiling the program that uses the class, and linking the object files resulting from these compilation steps together with any needed libraries to produce the final executable program.

Then we moved from the general to the specific, analyzing the particular data and functions that the StockItem class needed to perform its duties in an application program. The member variables needed for each StockItem object included the name, count, distributor, price, and UPC. Of course, merely having these member variables doesn't make a StockItem object very useful if it can't do anything with them. This led us to the topic of what member functions might be needed for such a class.

Rather than proceed immediately with the specialized member functions that pertain only to StockItem, however, we started by discussing the member functions that nearly every class needs to make its objects act like native variables. A class that has (at least) the capabilities of a native type is called a concrete data type. Such a class requires the following member functions:

1. A default constructor, which makes it possible to create an object of that class without supplying any initial data,
2. A copy constructor, which makes it possible to create a new object of this type with the same contents as an existing object of the same type,
3. An assignment operator, which copies the contents of one object of this type to another object of the same type,
4. A destructor, which performs whatever cleanup is needed when an object of this type "dies".
Since these member functions are so important to the proper functioning of a class, the compiler will create a version of each of them for us if we don't write them ourselves. In the case of StockItem, these compiler-generated member functions are perfectly acceptable, with the exception of the default constructor. The compiler-generated default constructor doesn't initialize a new StockItem object to a valid state, so we had to write that constructor ourselves to be sure of what a newly created StockItem contains. Next, we looked at the first version of a class interface specification for StockItem (Figure 6.3), which tells the user (and the compiler) exactly what functions objects of this class can perform. Some items of note in this construct are these:
1. The access specifiers public and private, which control access to the implementation of a class by functions not in the class (nonmember functions). Member variables and functions in the public section are available for use by nonmember functions, whereas member variables and functions in the private section are accessible only by member functions.
2. The declarations of the constructor functions, which construct a new object of the class. The first noteworthy point about constructors is that they have the same name as the class, which is how the compiler identifies them as constructors. The second point of note is that there can be more than one constructor for a given class; all constructors have the same name and are distinguished by their argument lists. This facility, called function overloading, is applicable to C++ functions in general, not just constructors. That is, you can have any number of functions with the same name as long as they have different argument lists; the difference in argument lists is enough to make the compiler treat them as different functions. In this case, we have written two constructors: the default constructor, which is used to create a StockItem when we don't specify an initial value, and a constructor that has arguments to specify values for all of the member variables.22
3. The declaration of a "normal" member function (that is, not a constructor or other predefined function) named Display, which as its name indicates, is used to display a StockItem on the screen.
4. The declarations of the member variables of StockItem, which are used to keep track of the information for a given object of the StockItem class.

Once we'd defined the class interface, we started on the class implementation by writing the default constructor for the StockItem class: StockItem::StockItem(). The reason for the doubled name is that when we write the implementation of a member function, we have to specify what class that member function belongs to. In this example, the first StockItem is the name of the class, whereas the second StockItem is the name of the function, which, as always with constructors, has the same name as the class. By contrast, we didn't have to specify the class name when declaring member functions in the interface definition, because all functions defined there are automatically member functions of that class. During this discussion, we saw that the preferred way to set the values of member variables is by using a member initialization list.

The next topic we visited was the scope of member variables, which is class scope. Each object of a given class has one set of member variables, which live as long as the object does. These member variables can be accessed from any member function as though they were global variables.

Then we examined how the default constructor was actually used in the example program, discovering that the line StockItem soup; was enough to cause it to be called. This is appropriate because one of the design goals of C++ was to allow a class object to be as easy to use as a native variable. Since a native variable can be created simply by specifying its type and name, the same should be true of a class object.

This led to a discussion of the fact that the person who writes a class isn't always the person who uses it. One reason for this is that the skills required to write a program using a class are not necessarily the same as those required to create the class in the first place.

Next, we covered the other constructor we wrote for the StockItem class. This one has arguments specifying the values for all of the member variables that make up the data part of the class.

Then we got to the final function of the first version of the StockItem class, the Display function, which as its name indicates is used to display the contents of a StockItem on the screen. This function uses the pre-existing ability of << to display shorts and strings, including those that hold the contents of a StockItem. The return type of this function is a type we hadn't seen before, void, which simply means that there is no return value from this function. We don't need a return value from the Display function because we call it solely for its side effect: displaying the value of its StockItem on the screen.

Next, we took up the private part of the StockItem class definition, which contains the member variables. We covered two reasons why it is a good idea to keep the member variables private: first, it makes debugging easier, because only the member functions can modify the member variables; second, we can change the names or types of our member variables or delete them from the class definition much more easily if we don't have to worry about what other functions might be relying on them. While we were on the subject of the member variables of StockItem, I also explained how we could use a short to store a price; by expressing the price in cents, rather than dollars and cents, any price up to $327.67 could be stored in such a variable.

As we continued with the analysis of how the StockItem objects would be used, we discovered that our example program actually needed a Vec of such objects, one for each different item in the stock. We also needed some way to read the information for these StockItem objects from a disk file, so we wouldn't have to type it in every time we started the program up. So the next program we examined provided this function via a C++ library class we hadn't seen before: ifstream (for input from a file). We also added a new function called Read to use ifstream to read information for a StockItem from the file containing that information.

While looking at the implementation of the new Read member function we ran into the idea of a reference argument, which is an argument that is another name for the caller's variable, rather than a copy of that variable (a value argument). This makes it possible to change the caller's variable by changing the value of the argument in the function. In most cases, we don't want to be able to change the caller's variable, but it is essential when reading from a stream, because otherwise we'd get the same data every time we read something from the stream. Therefore, we have to use a reference argument in this case, so that the stream's internal state will be updated correctly when we retrieve data from it.

Then we got to the question of how we could tell when there were no data left in the input file. The answer was to call the ifstream member function fail, which returns zero if some data remain in the stream and nonzero if we have tried to read past the end of the file. We used a nonzero return value from fail to trigger a break statement, which terminates whatever loop contains the break. In this case, the loop was the one that read data from the input file, so the loop would stop whenever we got to the end of the input file or when we had read 100 records, whichever came first.

This led to a detailed investigation of whether the number of records read was always calculated correctly. The problem under discussion was the potential for a fencepost error, also known as an off by one error. After careful consideration, I concluded that the code as written was correct.

Having cleared up that question, we proceeded to some other scenarios that might occur in the grocery store for which this program theoretically was being written. All of the scenarios we looked at had a common requirement: to be able to look up a StockItem, given some information about it. We first tried to handle this requirement by reading the UPC directly from each StockItem object in the Vec. When we found the correct StockItem, we would display and update the inventory for that StockItem. However, this didn't compile, because we were trying to access private member variables of a StockItem object from a nonmember function, which is illegal. While we could have changed those variables from private to public, that would directly contradict the reason that we made them private in the first place; that is, to prevent external functions from interfering in the inner workings of our StockItem objects. Therefore, we solved the problem by adding some new member functions (CheckUPC and DeductSaleFromInventory) to check the UPC of a StockItem and manipulate the inventory information for each StockItem, respectively. At the same time, we examined a new data type, bool, which is limited to the two values true and false; it is handy for keeping track of information such as whether we have found the StockItem object we are looking for and communicating such information back to a calling function.

While I was making these changes, I noticed that the original version of the test program wasn't very helpful to its user; it didn't tell the user whether the UPC was found, the name of the item, or how much inventory was available for sale. So I added some more member functions (GetInventory and GetName) to allow this more "user-friendly" information to be displayed.

Then we progressed to the second of the grocery store scenarios, in which the task was to find the price of an item, given its UPC. This turned out to be very similar to the previous problem of finding an item to update its inventory. Therefore, it was a pretty obvious step to try to make a function out of the "find an item by UPC" operation, rather than writing the code for the search again. Since we're doing "object-oriented" programming, such a function should probably be a member function. The question was "of which class?" It couldn't be a member function of StockItem, because the whole idea of this function was to locate a StockItem. A member function of StockItem needs a StockItem object to work on, but we didn't have the StockItem object yet.

The solution was to make another class, called Inventory, which had member functions to read the inventory information from the disk file (LoadInventory) and search it for a particular StockItem (FindItem). Most of this class was pretty simple, but we did run into an interesting design question: What should the FindItem function return if the UPC didn't match anything in the inventory? After some consideration, I decided to use a null object of the class StockItem; that is, one that exists solely to serve as a placeholder representing a non-existent object. This solution required adding an IsNull member function to the StockItem class, so that the user of FindItem could determine whether the returned object was "real" or just an indication of a UPC that wasn't found in the inventory.

Then we updated the test program to use this new means of locating a StockItem. Since the new version of the test program could perform either of two functions (price check or sale), we also added some output and input statements to ask the user what he wanted to do. To make this process more flexible, we allowed the user to type in either an upper or lower case letter to select which function to perform. This brought up the use of the "logical OR" operator || to allow the controlled block of an if statement to be executed if either (or both) of two expressions is true. We also saw how to combine an else with a following if statement, when we wanted to select among more than two alternatives.

We needed two more functions to make this new version of the application program work correctly: one to update the item in the inventory (Inventory::UpdateItem(StockItem Item)) and one to get the UPC of a StockItem (StockItem::GetUPC()). The reason that we had to add these new functions to the interfaces of Inventory and StockItem, respectively, is that we were 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 were 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 had to add the UpdateItem member function of Inventory that overwrote the original StockItem with our changed version. The GetUPC function's role in all this was to allow the UpdateItem function to look up the correct StockItem to be replaced without the main program having to pass the UPC in explicitly; instead, the GetUPC function allowed the UpdateItem function to retrieve the correct UPC from the updated object provided by the main program.

This brought us to the final scenario, which required us to look up the inventory for an item, given its UPC. As it happened, we had already solved that problem by the simple expedient of displaying the name and inventory of the StockItem as soon as it was located.

Finally, I mentioned a few other factors, such as alternative means of looking up an item without knowing its UPC, that would be important in writing a real application program and noted that we couldn't go into them here due to space limitations, but would deal with similar issues later in the book.

6.13. Exercises

1. In a real inventory control program, we would need to do more than merely read the inventory information in from a disk file, as we have done in this chapter. We'd also want to be able to write the updated inventory back to the disk file via an ofstream object, which is exactly like an ifstream object except that it allows us to write to a file rather than reading from one. Modify the header files item5.h and invent1.h to include the declarations of the new functions StockItem::Write and Inventory::StoreInventory, needed to support this new ability.
2. Implement the new functions that you declared in exercise 1. Then update the test program to write the changed inventory to a new file. To connect an ofstream called OutputStream to a file named "test.out", you could use the line:

ofstream OutputStream("test.out");

6.14. Conclusion

In this chapter, we've delved into the concepts and implementations of classes and objects, which are the constructs that make C++ an object-oriented language. Of course, we have only scratched the surface of these powerful topics; in fact, we'll spend much of the rest of this book on the fundamentals of classes and objects. Unfortunately, it's impossible to cover these constructs in every detail in any one book, no matter how long or detailed it may be, and I'm not going to try to do that. Instead, we'll continue with our in-depth examination of the basics of object-oriented programming. In the next chapter we'll start on the task of creating a string class almost, but not exactly, like the one from the standard C++ library that we've been using so far in this book.

6.15. Answers to Exercises

1. Here is the new function declaration that needs to be added to the StockItem interface definition (from code\item6.h):

void Write(ofstream& s);
and the one to be added to the Inventory interface definition (from code\invent2.h):

void StoreInventory(ofstream& OutputStream);

2. Figure 6.36 shows the implementation of the Write member function for StockItem, and Figure 6.37 is the implementation of the StoreInventory member function of the Inventory class. As you can see, neither of these functions is tremendously complex or, for that matter, very different from the Display function.
FIGURE 6.36. The Write member function for the StockItem class (from code\item6.cpp
void StockItem::Write(ofstream& os)
{
os << m_Name << endl;
os << m_InStock << endl;
os << m_Price << endl;
os << m_Distributor << endl;
os << m_UPC << endl;
return;
}

FIGURE 6.37. The StoreInventory member function for the Inventory class (from code\invent2.cpp)
void Inventory::StoreInventory(ofstream& os)
{
short i;

for (i = 0; i < m_StockCount; i ++)
m_Stock[i].Write(os);
}

Finally, Figure 6.38 shows the changes needed to the application program to write the updated inventory back to a new file.
FIGURE 6.38. The changes to the application program (from code\itemtst6.cpp)
ofstream OutputStream("shop2.out");
MyInventory.StoreInventory(OutputStream);

Of course in a real program, it would probably be better to write the updated inventory back to the original file, so that the next time we ran the program the updated inventory would be used. However, in the case of a test application, it's simpler to avoid modifying the input file so we can run the same test again if necessary.
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 itemtst6 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.

1 There are actually several other native C++ types that we haven't used: long, float, double, and bool. The long type is useful for storing whole-number values that are larger than will fit into a short (hence the name), while float and double are able to store values that have fractional parts as well as integral values. These are useful in scientific and engineering calculations; we'll use all of them except for float later in the book. The bool type, a relatively recent addition to C++, is useful for keeping track of a true/false condition. We'll see how to use this variable type later in this chapter.

2 Please note that the terms class and storage class have nothing to do with one another. This is another case where C++ reuses the same word for different concepts, although at least "storage class" isn't a keyword. We should be grateful for such small favors.

3 At least, that is the convention for classes created by "normal" programmers, like you and me. The header files that define the classes in the C++ standard library have no extensions and in fact may not even be stored in normal files. An example is the <string> header file that we have been using.

4 Some people use "object" to refer to a variable of any type, whether native or user-defined.

5 Purists may not approve of this use of the term object-oriented programming, as I'm not using this term in its strictest technical sense. However, since we are using objects and classes as our central organizing ideas, using the term object-oriented programming seems reasonable to me in this context. Chapters 9 and 10 will cover the other main concepts included in the strict definition of object-oriented programming.

6 In case you were wondering, you can't create new native types.

7 :: is the scope resolution operator when it does not follow a class or namespace name. This is another example of the confusing tendency of C++ to reuse the same sequence of characters to mean something different.

8 By the way, in using a reasonably functional class such as StockItem to illustrate these concepts, I'm violating a venerable tradition in C++ tutorials. Normally, example classes represent zoo animals, or shapes, or something equally useful in common programming situations.

9 However, there is one oddity about the declaration of a class when compared with other blocks: you need the ";" at the end of the block, after the closing "}", which isn't necessary for most other blocks. This is a leftover from C, as are many of the quirks of C++.

10 In case it isn't obvious how the compiler can figure out the size of the object, consider that the class definition specifies all of the variables that are used to implement the objects of the class. When we define a new class, the types of all of the member variables of the class must already be defined. Therefore, the compiler can calculate the size of our class variables based on the sizes of those member variables. By the way, the size of our object isn't necessarily the sum of the sizes of its member variables; the compiler often has to add some other information to the objects of a class besides the member variables. We'll see one of the reasons for this later in this book.

11 By the way, spaces between components of the name aren't significant; that is, we can leave them out as in Figure 6.4, or include them as in Figure 6.5.

12 Actually, I'm describing "normal" member variables here. There is another kind of member variable, called a static member variable, which is shared among all objects of a given class. We won't be using this type of member variable in this book.

13 The special status of member functions and variables as implementation aids explains why access specifiers such as public are applicable only to data or functions declared in a class, since the purpose of access specifiers is to control "outside" access to variables and functions used to implement a class. You can't apply access specifiers to native types, because the way these types are implemented is not accessible to the programmer.

14 Note that the names of the arguments are not part of the signature; in fact, you don't have to specify them in the function declaration. However, you should use meaningful argument names in the function declaration so the user of the function has some idea what the arguments to the function might represent. After all, the declaration StockItem(string, short, short, string, string); doesn't provide much information on what its arguments actually mean.

15 We'll see how to change the size of a Vec in Chapter 11.

16 As explained in the footnote on page 613.

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.

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.

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.

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.

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.

22 The compiler has also supplied a copy constructor for us, so that we can use StockItem objects as function arguments and return values. In this case, the compiler-generated copy constructor does exactly what we want, so we don't have to write our own. As we'll see in the rest of the book, these compiler-generated functions don't always behave properly, especially with more complicated classes than the StockItem class, where the compiler can't figure out how to copy or assign objects correctly without more help from us.


TOC PREV NEXT INDEX