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 10 Polymorphism
By the end of the previous chapter, we had created a DatedStockItem class by inheritance from the StockItem class, adding an expiration date field. This was a solution to the problem of creating a new class based on the existing StockItem class without rewriting all of the already functioning code in that class. Unfortunately, however, it didn't allow us to mix objects of the original StockItem class in the same Vec with those of the new DatedStockItem class and still have the correct Reorder function called for the derived class object. So far in this book, we've seen how and why we use the first two major organizing principles of object-oriented programming: encapsulation and inheritance. Now our requirement of mixing base and derived class objects leads us to the third and final major organizing principle: polymorphism. Once we have defined some terms, we'll get right to using polymorphism to solve our problem of freely interchanging StockItems and DatedStockItems in our application programs.
10.1. Definitions
Static typing means determining the exact type of a variable when the program is compiled. It is the default typing mechanism in C++. Note that this has no particular relation to the keyword static.
Dynamic typing means delaying the determination of the exact type of a variable until run time rather than fixing that type at compile time, as in static typing.
Polymorphism, one of the major organizing principles in C++, allows us to implement several classes with the same interface and treat objects of all these classes as though they were of the same class. Polymorphism is a variety of dynamic typing that maintains the safety factor of static type-checking, because the compiler can determine at compile time whether a function call is legal even if it does not know the exact type of the object that will receive that function call at run time. Polymorphism is derived from the Greek poly, meaning "many", and morph, meaning "form". In other words, the same behavior is implemented in different forms.
Declaring a function to be virtual means that it is a member of a set of functions having the same signatures and belonging to classes related by inheritance. The actual function to be executed as the result of a given virtual function call is selected from this set of functions dynamically (i.e., at run time) based on the actual type of an object referred to via a base class pointer (or base class reference). This is the C++ dynamic typing mechanism used to implement polymorphism, in contrast to the static typing used for non-virtual functions, which are selected at compile time.
Reference counting is a mechanism that allows one copy of an object to be shared among a number of users while arranging for the automatic disposal of that object as soon as no one is using it.

10.2. Objectives of This Chapter

By the end of this chapter, you should
1. Understand how we can use polymorphism to allow objects of different classes to be treated interchangeably by the user of these classes,
2. Understand how to create a polymorphic object that allows polymorphism to be used safely in application programs without exposing the class user to the hazards of pointers, and
3. Understand how to use reference counting to allow one data item to be safely shared among several users.

10.3. Introduction to Polymorphism

To select the correct function to be called based on the actual type of an object at run time, we have to use polymorphism. Polymorphic behavior of our StockItem and DatedStockItem classes means that we can (for example) mix StockItem and DatedStockItem objects in a Vec and have the right Reorder function executed for each object in the Vec.

Susan wanted to know the motivation for using polymorphism here:

Susan: Why would you want to handle several different types of data as though they were the same type?
Steve: Because the objects of these two classes perform the same operation, although in a slightly different way, which is why they can have the same interface. In our example, a DatedStockItem acts just like a StockItem except that it has an additional data field and produces different reordering information. Ideally, we would be able to mix these two types in the application program without having to worry about which class each object belongs to except when creating an individual item (at which time we have to know whether the item has an expiration date).
Susan: Yes, but I don't understand why we need to do this in the first place. Why don't we just have two Vecs, one for the StockItem objects and one for the DatedStockItem objects?
Steve: Yes, it would be possible to do that. But it would make the program more complicated and wouldn't allow for adding further derived classes in a simple way. Imagine how messy the program would be if we had 10 derived classes instead of just one!

Susan also had a question about the relationship between base and derived classes:

Susan: What do the base and derived classes share besides an interface?
Steve: The derived class contains all of the member variables of the base class and can access those member variables or call any of the member functions of the base class, if they are public or protected. Of course, the derived class can also add whatever new member functions and member variables it needs.
However, there is a serious complication in using polymorphism: we have to refer to the objects via a pointer rather than directly.1 While C++ does have a "native" means of doing this, it exposes us to all the dangers of pointers, both those that you're already acquainted with and others that we'll get to later in this chapter.

Susan wanted more details on why pointers are dangerous; here's the first installment of our discussion of this point.

Susan: You keep saying that pointers are dangerous; what do they do that is so dangerous?
Steve: It's not what they do but what their users do: mostly, create memory leaks and dangling pointers (which point to memory that has already been freed).
Susan: So pointers are dangerous because it is just too easy to make mistakes when you use them?
Steve: Yes. In theory, pointers are fine, which is probably why they're so popular in computer science courses. In practice, however, they are very error-prone.
The ideal solution to this problem is to confine pointers to the interior of classes we design so that we can keep track of them ourselves and let the application programmer worry about getting the job done. As it happens, this is possible; thus, we can obtain the benefits of polymorphism without exposing the application programmer (as opposed to the class designers; i.e., us) to the hazards of pointers. We'll see how to do that later in this chapter.

But before investigating that more sophisticated method of providing polymorphism, we need to understand the workings of the native polymorphism mechanism in C++. As we saw in Chapter 9, the address of a derived class object can be assigned to a pointer declared to be a pointer to a base class of that derived class. While this does not by itself solve the problem of calling the correct function in these circumstances, there is a way to get the behavior we want. If we define a special kind of function called a virtual function and refer to it through a pointer (or a reference) to an object, the version of that function to be executed will be determined by the actual type of the object to which the pointer (or reference) refers, rather than by the declared type of the pointer (or reference). This implies that if we declare a function to be virtual, when a function with that signature is called via a base class pointer, the actual function to be called is selected at run time rather than at compile time, as happens with non-virtual functions. Clearly, if the actual run-time type of the object determines which version of the function is called, the compiler can't select the function at compile time.

Because the determination of the function to be called is delayed until run time, the compiler has to add code to each function call to make that determination. This code uses a construct called a vtable to keep track of the locations of all the functions for a given type of object so that the compiler-generated code can find the right function when the call is about to be executed.

As you might imagine, Susan had some questions about this notion of virtual function calls. Here's the beginning of that discussion:

Susan: I don't understand how the function to be executed is selected.
Steve: The mechanism depends on whether it is a virtual function. If not, the linker can figure out the exact address of the function when it is linking the program, because the type of the pointer (which is known at compile time) is used to determine which function will be called. On the other hand, with a virtual function declaration, the function to be executed depends on the actual type of the object pointed to rather than the type of the pointer to the object; since that information can't be known at compile time, the linker can't make the determination of which function to call. Therefore, in such cases, the compiler sticks code in the executable program that figures it out at run time by consulting the vtable for the particular type of object the base class pointer refers to.

The virtual Keyword

But exactly how does this help us with our Reorder function? Let's see how a virtual function affects the behavior of our final example program from Chapter 9 (nvirtual.cpp, Figure 9.39). Figure 10.1 shows the same interface as before, except that StockItem::Reorder is declared to be virtual.2 Because the current test program (virtual.cpp) and implementation file (itemb.cpp) are almost identical to the final test program (nvirtual.cpp) and implementation file (itema.cpp) in Chapter 9, differing only in that the new ones #include "itemb.h" rather than "itema.h", I haven't reproduced the new versions of those files.

If you printed out the corresponding files from the previous chapter, you might just want to mark them up to indicate these changes. Otherwise, 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 go through this section of the chapter; those files are itemb.h, itemb.cpp, and virtual.cpp, respectively.

FIGURE 10.1. Dangerous polymorphism: Interfaces of StockItem and DatedStockItem with virtual Reorder function (code\itemb.h)
// itemb.h

class StockItem
{
public:
StockItem(std::string Name, short InStock, short MinimumStock);
virtual void Reorder(std::ostream& os);

protected:
std::string m_Name;
short m_InStock;
short m_MinimumStock;
};

class DatedStockItem: public StockItem // deriving a new class
{
public:
DatedStockItem(std::string Name, short InStock, short MinimumStock,
std::string Expires);

virtual void Reorder(std::ostream& os);

protected:
static std::string Today();

protected:
std::string m_Expires;
};


Figure 10.2 shows the output of the new test program.

FIGURE 10.2. virtual function call example output (code\virtual.out)
StockItem::Reorder says:
Reorder 68 units of soup

DatedStockItem::Reorder says:
Return 10 units of milk
StockItem::Reorder says:
Reorder 15 units of milk

StockItem::Reorder says:
Reorder 70 units of beans

DatedStockItem::Reorder says:
Return 22 units of ham
StockItem::Reorder says:
Reorder 30 units of ham

DatedStockItem::Reorder says:
Return 90 units of steak
StockItem::Reorder says:
Reorder 95 units of steak


Notice that the output of this program is exactly the same as the output of the previous test program (Figure 9.39 on page 647), except for the last entry. With the non-virtual Reorder function in the previous program, we got the following output:
StockItem::Reorder says:
Reorder 5 units of steak

whereas with our virtual Reorder function, we get this output:
DatedStockItem::Reorder says:
Return 90 units of steak

StockItem::Reorder says:
Reorder 95 units of steak

According to our rules, the correct answer is 95 units of steak because the stock has expired, so the program that uses the virtual Reorder function works correctly while the previous one didn't. Why is this? Because when we call a virtual function through a base class pointer, the function executed is the one defined in the class of the actual object to which the pointer points, not the one defined in the class of the pointer.

To see how this works, let's start by looking at the way in which the layout of an object with virtual functions differs from that of a "normal" object. First, Figure 10.3 shows a possible memory representation of a simplified StockItem without virtual functions.

One of the interesting points about this figure is that there is no connection at run time between the StockItem object and its functions. Such a connection is unnecessary because the compiler can tell exactly which function will be called whenever a function is referenced for this object, whether directly or through a pointer, and therefore can provide the linker with enough information to generate a call directly to the appropriate function.

FIGURE 10.3. A simplified StockItem object without virtual functions
The situation is different if we have virtual functions. In that case, the compiler can't determine exactly which function will be called for an object pointed to by a StockItem* because the actual object may be a descendant of StockItem rather than an actual StockItem. If so, we want the function defined in the derived class (e.g., DatedStockItem) to be called even though the pointer is declared to point to an object of the base class (e.g., StockItem).

Since the actual type of the object for which we want to call the function isn't available at compile time, another way must be found to determine which function should be called. The most logical place to store this information is in the object itself, because after all, we need to know where the object is in order to call the function for it. In fact, an object of a class for which any virtual functions are declared does have an extra data item in it for exactly this purpose. So whenever a call to a virtual function is compiled, the compiler translates that call into instructions that use the information in the object to determine at run time which version of the virtual function will be called.

Here's the next installment of my discussion with Susan on the topic of virtual functions:

Susan: So, is a virtual function polymorphism?
Steve: Not quite. You need virtual functions to implement polymorphism in C++, but they're not the same thing.
Susan: Where in the definition of Reorder does it say it's virtual? The implementation file is the same as it was before.
Steve: It's in the declaration of Reorder in the interface of the StockItem class in the itemb.h header file: virtual void Reorder(ostream& os);. I've also repeated it in the derived class function declaration even though that's not strictly necessary. After a function is declared as virtual in a base class, we don't have to say it's virtual in the derived class or classes; the rule is "once virtual, always virtual".
If every object needed to contain the addresses of all its virtual functions, objects might be a lot larger than they would otherwise have to be. However, this is not necessary because all objects of the same class have the same virtual functions. Therefore, the addresses of all of the virtual functions for a given class are stored in a virtual function address table, or vtable for short, and every object of that class contains the address of the vtable for that class.

Given this description of the vtable, if we make the Reorder function virtual, a StockItem object will look something like Figure 10.4, and a DatedStockItem will resemble Figure 10.5.3

Susan had some more questions about vtables:

Susan: Are vtables customized for each class?
Steve: Yes.
Susan: Where do they come from, how are they created, and how do they do what they do?
Steve: The linker creates them based on instructions from the compiler after the compiler examines the class definition. All they do is store the addresses of the virtual functions for that class so that the compiler can generate code that will select the correct function for the object being referred to at run time.
FIGURE 10.4. Dangerous polymorphism: A simplified StockItem object with a virtual function
Susan: How is this different from derivation?
Steve: It's part of making derivation work correctly when we want to use pointers to the base class, and mix base and derived class objects in our program.
Susan: I don't get this vtable stuff. Does it just point the Reorder function in the proper direction at run time?
Steve: Not exactly. It allows the program to pick the correct Reorder function at run time.
Susan: This stuff is beyond "UGH!". It is just outrageous.
Steve: It wasn't that easy for me either. Acquiring a full understanding of virtual functions is one of the major milestones in learning C++, even for programmers with substantial experience in other languages.
FIGURE 10.5. Dangerous polymorphism: A simplified DatedStockItem object with a virtual function

Now that we have declared Reorder as a virtual function, let's see how this affects the operation of the function call examples we saw in Chapter 9 (Figures 9.36 through 9.38). First, Figure 10.6 shows how a virtual (i.e., dynamically determined) function call works when Reorder is called for a StockItem object through a StockItem pointer such as SIPtr.
FIGURE 10.6. Dangerous polymorphism: Calling a virtual Reorder function through a StockItem pointer to a StockItem object

The net result of the call illustrated in Figure 10.6 is the same as that illustrated in Figure 9.36: StockItem::Reorder is called, which is correct in this situation. Next, Figure 10.7 shows a virtual call for a DatedStockItem object through a DatedStockItem pointer.

FIGURE 10.7. Dangerous polymorphism: Calling a virtual Reorder function through a DatedStockItem pointer to a DatedStockItem object
Again, the net result of the call illustrated in Figure 10.7 is the same as that illustrated in Figure 9.37: DatedStockItem::Reorder is called. This is correct in this situation. Finally, Figure 10.8 shows a virtual call for a DatedStockItem object through a StockItem pointer.
FIGURE 10.8. Dangerous polymorphism: Calling a virtual Reorder function through a StockItem pointer to a DatedStockItem object
Figure 10.8 is where the virtual function pays off. The correct function, DatedStockItem::Reorder, is called even though the type of the pointer through which it is called is StockItem*. This is in contrast to the result of that same call with the non-virtual function, illustrated in Figure 9.38. In that case, StockItem::Reorder rather than DatedStockItem::Reorder was called.

Susan had a question about those last few example programs:

Susan: I didn't see where you ever deleted the memory for those pointers. Wouldn't that cause a memory leak?
Steve: Oops, you're right. That's a good example of how easy it is to misuse dynamic memory allocation!
What happens if we add another virtual function, Write, for instance, to the StockItem class after the Reorder function? The new virtual function will be added to the vtables for both the StockItem and DatedStockItem classes. Then the situation for a StockItem object might look like Figure 10.9, and the situation for a DatedStockItem might look like Figure 10.10.

As you can see, the new function has been added to both vtables, so a call to Write through a base class pointer will call the correct function.

To translate this virtual function mechanism into what I hope is understandable English, we can express the call to the virtual function Write in the line SIPtr->Write(cout); as follows:

1. Get the vtable address from the object whose address is in SIPtr.
2. Since we are calling Write through a StockItem*, and Write is the second defined virtual function in the StockItem class, retrieve the address of the Write function from the second function address slot in the vtable for the actual object that the StockItem* points to.
3. Execute the function at that address.
FIGURE 10.9. Dangerous polymorphism: A simplified StockItem object with two virtual functions
By following this sequence, you can see that while both versions of Write are referred to via the same relative position in both the StockItem and the DatedStockItem vtables, the particular version of Write that is executed depends on which vtable the object refers to. Since all objects of the same class have the same member functions, all StockItem objects point to the same StockItem vtable and all DatedStockItem objects point to the same DatedStockItem vtable.
FIGURE 10.10. Dangerous polymorphism: A simplified DatedStockItem with two virtual functions

Susan had some questions about adding a new virtual function:

Susan: What do you mean by "added to both vtables"? Do StockItem and DatedStockItem each have their own?
Steve: Yes.
Susan: How does the vtable get the address for the new StockItems?
Steve: It's the other way around. Each StockItem, when it's created by the constructor, has its vtable address filled in by the compiler automatically.

Problems with Using Pointers for Polymorphism

Unfortunately, it's not quite as simple to make polymorphism work for us as this might suggest. As is so often the case, the culprit is the use of pointers. To see how pointers cause trouble with polymorphism, let's start by adding the standard I/O functions, operator << and operator >>, to our simplified interface for the StockItem and DatedStockItem classes. Figure 10.11 shows a test program illustrating how we can use these new functions, Figure 10.12 shows the output of the test program, and Figure 10.13 shows the new version of the interface. I strongly recommend that you print out that header file and the test program for reference as you leaf through this section of the chapter; the latter file is polyioa.cpp.
FIGURE 10.11. Dangerous polymorphism: Using operator << with a StockItem* (code\polyioa.cpp)
#include <iostream>
#include "Vec.h"
#include "itemc.h"
using namespace std;

int main()
{
Vec <StockItem*> x(2);

x[0] = new StockItem("3-ounce cups",71,78);

x[1] = new DatedStockItem("milk",76,87,"19970719");

cout << "A StockItem: " << endl;
cout << x[0] << endl;

cout << "A DatedStockItem: " << endl;
cout << x[1] << endl;

delete x[0];
delete x[1];

return 0;
}

FIGURE 10.12. Result of using operator << with a StockItem* (code\polyioa.out)

A StockItem:

0

3-ounce cups

71

78

A DatedStockItem:

19970719

milk

76

87

FIGURE 10.13. Dangerous polymorphism: StockItem interface with operator << and operator >> (code\itemc.h)
class StockItem
{
friend std::ostream& operator << (std::ostream& os, StockItem* Item);
friend std::istream& operator >> (std::istream& is, StockItem*& Item);

public:
StockItem(std::string Name, short InStock, short MinimumStock);
virtual ~StockItem();

virtual void Reorder(std::ostream& os);
virtual void Write(std::ostream& os);

protected:
std::string m_Name;
short m_InStock;
short m_MinimumStock;
};

class DatedStockItem: public StockItem
{
public:
DatedStockItem(std::string Name, short InStock,
short MinimumStock, std::string Expires);

virtual void Reorder(std::ostream& os);
virtual void Write(std::ostream& os);

protected:
static std::string Today();

protected:
std::string m_Expires;
};

Susan had some questions about the StockItem::~StockItem destructor declared in this latest version of the interface.
Susan: Why do we need a destructor for StockItem now, when we didn't need one before?
Steve: The reason we haven't needed a destructor for the StockItem class until now is that the compiler-generated destructor works fine as long as two conditions are present. First, the member variables of the class must all be of concrete data types (which they are here). Second, the class must have no virtual functions, which of course isn't true for StockItem anymore. We've discussed the reason for the first condition: if we have member variables that are not of concrete data types (e.g., pointers), they won't clean up after themselves properly. We'll find out exactly why the second condition is important as soon as we get through looking at the output of the sample program.
Susan: Okay, I'm sure I can wait. But why is the destructor virtual?
Steve: We'll cover that at the same time.
The first item of note in the test program in Figure 10.11 is that we can create a Vec of StockItem*s to hold the addresses of any mixture of StockItems and DatedStockItems, because we can assign the addresses of variables of either of those types to a base class pointer (i.e., a StockItem*). Once we have the Vec of StockItem*s, we use operator new to acquire the memory for whichever type of object we're creating. This allows us to access these objects via pointers rather than directly and thus to use polymorphism. Once we finish using the objects, we have to make sure they are properly disposed of by calling operator delete at the end of the program; otherwise, a memory leak results.

The calls to delete in Figure 10.11 also hold the key to Susan's question about why we needed to write a destructor for this new version of the StockItem class. You see, when we call operator delete for an object of a class type, delete calls the destructor for that object to do whatever cleanup is necessary at the end of the object's lifespan. For this reason, it is very important that the correct destructor is called. If a base class destructor were called instead of a derived class destructor, the cleanup of the fields defined in the derived class wouldn't occur. However, when we delete a derived class object through a base class pointer, as we are doing in the current example program, the compiler can't tell at compile time which destructor it should call when the program executes. What do we do when we need to delay the determination of the exact version of a function until run time? We use a virtual function. Therefore, whenever we want to call delete on an object through a base class pointer, we need to make the destructor for that object virtual.4

But that still doesn't explain exactly why we need a virtual destructor whenever we have any other virtual functions. The reason for that rule is that there isn't much point in referring to an object through a base class pointer if it doesn't have any virtual functions, because the correct function will never be called in that case! Therefore, although the strict rule is "the destructor must be virtual if there are any calls to delete through a base class pointer", that amounts to the same thing as "the destructor must be virtual if there are any other virtual functions in the class", and the latter rule is easier to remember and follow.

Now let's take a look at the new implementation of the StockItem class, which is shown in Figure 10.14. This code is in code\itemc.cpp if you want to print it out for reference.

FIGURE 10.14. Dangerous polymorphism: StockItem implementation with operator << and operator >> (code\itemc.cpp)
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include "itemc.h"
#include <dos.h>
using namespace std;

StockItem::StockItem(string Name, short InStock,
short MinimumStock)
: m_InStock(InStock), m_Name(Name),
m_MinimumStock(MinimumStock)
{
}

StockItem::~StockItem()
{
}

void StockItem::Reorder(ostream& os)
{
short ReorderAmount;

if (m_InStock < m_MinimumStock)
{
ReorderAmount = m_MinimumStock-m_InStock;
os << "Reorder " << ReorderAmount << " units of " << m_Name;
}
}

ostream& operator << (ostream& os, StockItem* Item)
{
Item->Write(os);
return os;
}

void StockItem::Write(ostream& os)
{
os << 0 << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_MinimumStock << endl;
}

istream& operator >> (istream& is, StockItem*& Item)
{
string Expires;
short InStock;
short MinimumStock;
string Name;

getline(is,Expires);
getline(is,Name);
is >> InStock;
is >> MinimumStock;
is.ignore();

if (Expires == "0")
Item = new StockItem(Name,InStock,MinimumStock);
else
Item = new DatedStockItem(Name,InStock,
MinimumStock,Expires);

return is;
}

void DatedStockItem::Reorder(ostream& os)
{
if (m_Expires < Today())
{
os << "DatedStockItem::Reorder says:" << endl;
os << "Return " << m_InStock << " units of ";
os << m_Name << endl;
m_InStock = 0;
}

StockItem::Reorder(os);
}

string DatedStockItem::Today()
{
struct date d;
unsigned short year;
unsigned short day;
unsigned short month;
string TodaysDate;
stringstream FormatStream;

getdate(&d);
year = d.da_year;
day = d.da_day;
month = d.da_mon;

FormatStream << setfill(`0') << setw(4) << year <<
setw(2) << month << setw(2) << day;
FormatStream >> TodaysDate;

return TodaysDate;
}

DatedStockItem::DatedStockItem(string Name, short InStock,
short MinimumStock, string Expires)
: StockItem(Name, InStock,MinimumStock),
m_Expires(Expires)
{
}

void DatedStockItem::Write(ostream& os)
{
os << m_Expires << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_MinimumStock << endl;
}

Susan had some questions about the test program and how it relates to the implementation file for the StockItem classes.
Susan: Why do you need the same headers in the test program as you do in the implementation file?
Steve: Because otherwise the compiler doesn't know how to allocate memory for a StockItem or what functions it can perform.
Susan: I didn't know that the use of headers also allocates memory.
Steve: It doesn't. However, the compiler needs the headers to figure out how large every object is so it can allocate storage for each object.
Susan: How does it figure that out?
Steve: It adds up the sizes of all the components in the object you're defining. For example, if you've defined a StockItem to contain three shorts and two strings, then a StockItem object will have to be big enough to contain three shorts plus the size of two strings, with possibly some additional space for other stuff the compiler knows about, such as a vtable pointer.
Susan: Why do you have to allocate storage anyway? I mean, why can't you just tell the compiler how much memory you have left and let it use as much as it wants until the memory is used up? Then you know you're done. <g>
Steve: It does use as much memory as it needs, but it has to know how much of the memory it needs to set aside for each object that you create.
Susan: So, if you have a string class in the implementation of a program and you intend to use it in the interface, then it has to be included in both because they both get compiled separately?
Steve: Sort of. An interface (i.e., a header file) doesn't get compiled separately; it's #included wherever it's needed.
Susan: Yes, but why does it need to be in both places; why isn't one place good enough?
Steve: Because each .cpp file is compiled separately; when the compiler is handling any particular .cpp file, it doesn't know about any header file that isn't mentioned in that file. Therefore, we have to mention every header in every .cpp file that uses objects defined in that header.
Susan: So they are compiled separately. How are they ever connected, and if they do become connected, why is it necessary to write them twice?
Steve: They are connected only by the linker. You don't need to write them twice.
Susan: If they are included in the implementations, aren't they included in the test programs automatically?
Steve: No, because the test program is compiled separately from the implementations. In fact, the writer of the test program may not even have the source code for the implementations, such as when you buy certain libraries that come without source code.
Susan: And if they are needed, then why aren't the other header files needed in the test programs or any other programs for that matter?
Steve: You only have to include those header files that the compiler needs to figure out the size and functions of any object you use.
Susan: Yes, but then if they're necessary for the implementation, then they should be needed for the test programs, I would think. I still don't get it.
Steve: Each header file is needed only in source files that refer to the objects whose classes are defined in that header file. For example, if you aren't using strings in your program, then you don't have to #include <string>.
Susan: Well, if you're writing an implementation for a program, then I think that every source file that uses the class needs to include all the header files, no?
Steve: Yes, except that sometimes you have objects that are used only inside the implementation of a class, as we'll see later in this chapter.
Let's start our analysis of the new versions of the I/O functions with the declaration of operator <<, which is friend ostream& operator << (ostream& os, StockItem* Item);. The second argument to this function is a StockItem* rather than a StockItem because we have to refer to our StockItem and DatedStockItem objects through a base class pointer (i.e., a StockItem*) to get the benefits of polymorphism. Although operator << isn't a virtual function (since it's not a member function at all), we will see that it still makes use of polymorphism.

Susan wanted to know why we needed new I/O functions again:

Susan: Why are you explaining >> and << again? Why won't the old ones do?
Steve: Because the old ones can use StockItems directly, whereas the new ones have to operate on StockItem*s instead. Whenever you change the types of arguments to a function, you have to change the function also. This particular change is part of what is wrong with the standard method of using virtual functions to achieve polymorphism.
Susan: Why aren't you showing how polymorphism is done with real data instead of these >> and << things again?
Steve: This is how polymorphism is done with real data, if we expose the pointers to the application program.
Susan: Do you have to write everything that you see in your classes? Do you even have to define your periods?
Steve: No, as a matter of fact, the "." operator is (unfortunately) one of the few operators that can't be redefined.
The next point worthy of discussion is that we can use the same operator << to display either a StockItem or a DatedStockItem even though the display functions for those two types are actually different. Let's look at the implementation of this version of operator <<, shown in Figure 10.15.
FIGURE 10.15. Dangerous polymorphism: The implementation of operator << with a StockItem* (from code\itemc.cpp)
ostream& operator << (ostream& os, StockItem* Item)
{
Item->Write(os);
return os;
}

Susan had a question about the argument list for this function:
Susan: Why do we need the argument os?
Steve: Because that's where we want the output to go.
This implementation looks pretty simple, as it merely calls a function called Write to do the actual work. In fact, this code looks too simple: How does it decide whether to display a StockItem or a DatedStockItem?

Using a virtual Function for I/O

This is an application of polymorphism: operator << doesn't have to decide whether to call the version of Write in the StockItem class or the one in the DatedStockItem class because that decision is made automatically at run time. Write is a virtual function declared in the StockItem class; therefore, the exact version of Write called through a StockItem* is determined by the run-time type of the object that the StockItem* actually points to.

To complete the explanation of how operator << works, we'll need to examine Write. Let's look at its implementation for the simplified versions of our StockItem (Figure 10.16) and DatedStockItem (Figure 10.17) classes.

FIGURE 10.16. Dangerous polymorphism: StockItem::Write (from code\itemc.cpp)
void StockItem::Write(ostream& os)
{
os << 0 << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_MinimumStock << endl;
}

FIGURE 10.17. Dangerous polymorphism: DatedStockItem::Write (from code\itemc.cpp)
void DatedStockItem::Write(ostream& os)
{
os << m_Expires << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_MinimumStock << endl;
}

The only thing that might not be obvious about these functions is why StockItem::Write writes the "0" out as its first action. We know that there's no date for a StockItem, so why not just write out the data that it does have? The reason is that if we want to read the data back in, we need some way to distinguish between a StockItem and a DatedStockItem. Since "0" is not a valid date, we can use it as an indicator meaning "the following data belongs to a StockItem, not to a DatedStockItem". In other words, when we read data from the inventory file to create our StockItem and DatedStockItem objects, any set of data that starts with a "0" will produce a StockItem while any set that starts with a valid date will produce a DatedStockItem.

If this still isn't perfectly clear, don't worry. The next section, which covers operator >>, should clear it up.

References to Pointers

First, let's examine the header of the operator >> function:
istream& operator >> (istream& is, StockItem*& Item)
Most of this should be familiar by now, but there is one oddity: the declaration of the second argument to this function is StockItem*&. What does that mean?

It's a reference to a pointer. Now, before you decide to throw in the towel, recall that we use a reference argument when we need to modify a variable in the calling function. In this case, that variable is a StockItem* (a pointer to a StockItem or one of its derived classes), and we are going to have to change it by assigning the address of a newly created StockItem or DatedStockItem to it. Hence, our argument has to be a reference to the variable in the calling function; since that variable is a StockItem*, our argument has to be declared as a reference to a StockItem*, which we write as StockItem*&.

Having cleared up that point, let's look at how we would use this new function (Figure 10.18). In case you want to print out the file containing this code, it is polyiob.cpp.

Susan had a question about the argument to the ifstream constructor:

Susan: What is polyiob.in?
Steve: It's the data file we're going to read the data from.
FIGURE 10.18. Dangerous polymorphism: Using operator >> and operator << with a StockItem* (code\polyiob.cpp)
#include <iostream>
#include <fstream>
#include "Vec.h"
#include "itemc.h"
using namespace std;

int main()
{
StockItem* x;
StockItem* y;

ifstream ShopInfo("polyiob.in");

ShopInfo >> x;

ShopInfo >> y;

cout << "A StockItem: " << endl;
cout << x;

cout << endl;

cout << "A DatedStockItem: " << endl;
cout << y;

delete x;
delete y;

return 0;
}

Before we continue to analyze this program, look at Figure 10.19, which shows the output it produces.
FIGURE 10.19. Dangerous polymorphism: The results of using operator >> and operator << with a StockItem* (code\polyiob.out)

A StockItem:

0

3-ounce cups

71

78

A DatedStockItem:

19970719

milk

76

87


Now let's get back to the code. If you are really alert, you may have noticed something odd here. How can we assign a value to a variable such as x or y without allocating any memory for it? For that matter, how can we call operator delete for a pointer variable that hasn't had memory assigned to it? In fact, these aren't errors but consequences of the way we have to implement operator >> with the tools we have so far. To see why this is so, let's take a look at that implementation, in Figure 10.20.

This starts out reasonably enough by declaring variables to hold the expiration date (Expires), number in stock (InStock), minimum number desired in stock (MinimumStock), and name of the item (Name). Then we read values for these variables from the istream supplied as the left-hand argument in the operator >> call, which in the case of our example program is ShopInfo. Next, we examine the variable Expires, which was the first variable to be read in from the istream. If the value of Expires is "0", meaning "not a date", we create a new StockItem by calling the normal constructor for that class and assigning memory to that new object via operator new. If the Expires value isn't "0", we assume it's a date and create a new DatedStockItem by calling the constructor for DatedStockItem and assigning memory for the new object via operator new. Finally, we return the istream so it can be used in further operator >> calls.

The fact that we have to create a different type of object in these two cases is the key to why we have to allocate the memory in the operator >> function rather than in the calling program. The actual type of the object isn't known until we read the data from the file, so we can't allocate memory for the object until that time. This isn't necessarily a bad thing in itself; the trouble is that we can't free the memory automatically because the calling program owns the StockItem pointers and has to call delete to free the memory allocated to those pointers when the objects are no longer needed.

FIGURE 10.20. Dangerous polymorphism: The implementation of operator >> (from code\itemc.cpp)
istream& operator >> (istream& is, StockItem*& Item)
{
string Expires;
short InStock;
short MinimumStock;
string Name;

getline(is,Expires);
getline(is,Name);
is >> InStock;
is >> MinimumStock;
is.ignore();

if (Expires == "0")
Item = new StockItem(Name,InStock,MinimumStock);
else
Item = new DatedStockItem(Name,InStock,
MinimumStock,Expires);

return is;
}

While it is legal (and unfortunately not unusual) to write programs in which memory is allocated and freed in this way, it isn't a good idea. The likelihood of error in any large program that uses this method of memory management is approximately 100%. Besides the problem of forgetting to free memory or using memory that has already been freed, we also have the problem that copying pointers leaves two pointers pointing to the same data, which makes it even more likely that the data will either be freed prematurely or not freed at all when it is no longer in use.

Susan had some questions about the dangers of pointers:

Susan: So the programmer forgets to free memory?
Steve: Yes.
Susan: Can't you just write the code to free the memory once?
Steve: Yes, unless you ever change the program.
Susan: Or can these bad things happen on their own even if the program is written properly?
Steve: Yes, that can happen under certain circumstances, but luckily we won't run into any of those circumstances in this book.
We'll begin to solve these problems right after some exercises.

10.4. Exercises, First Set

1. Rewrite the DrugStockItem class that you wrote in Chapter 9 as a derived class of DatedStockItem, using virtual functions to allow DrugStockItem objects to be used in place of StockItem objects or DatedStockItem objects, just as you can use DatedStockItem objects in place of StockItem objects.
2. Rewrite the Employee class that you wrote in Chapter 9 as three classes: a base Employee class, an Exempt class and an Hourly class. The latter two classes will be derived from the base class. The virtual CalculatePay member function for each of these derived classes should use the appropriate method of calculating the pay for each class so that an Exempt object or an Hourly object can be substituted for an Employee class object. The Employee class CalculatePay function should display an error message, as that class does not have a method of calculating pay. Note that unlike the first Employee exercise in Chapter 9, you must maintain the same interface for the CalculatePay function in these classes because you are using a base class pointer to derived class objects.

10.5. Polymorphic Objects

As we have just seen, the "standard" method of adding polymorphism to our programs is, to use a technical term, ugly; that is, it is error prone and virtually impossible to maintain. After a few more definitions, we're going to see how to fix these problems with an advanced technique I refer to as polymorphic objects.

10.6. More Definitions

A polymorphic object is a C++ object that presents the appearance of a simple object while behaving polymorphically, but without the hazards of exposing the user of the polymorphic object to the pointers used within its implementation. The user of a polymorphic object does not have to know about any of the details of the implementation, but merely creates an object of the single visible class (the manager class). That object does what the user wants with the help of an object of a worker class, which is derived from the manager class.
The manager/worker idiom is a mechanism that allows the effective type of an object to be determined at run time without requiring the user of the object to be concerned with pointers.5
The reference-counting idiom is a mechanism that allows one object (the reference-counted object) to be shared by several other objects (the client objects); thus, a copy needn't be made for each of the client objects.

10.7. Why We Need Polymorphic Objects

You may be wondering what an "idiom" is in programming. Well, in English or any other natural language, an idiom is a phrase whose meaning can't be derived directly from the meanings of its individual words. An example would be "to make good time", which actually means "to proceed rapidly". Similarly, the manager/worker idiom used to implement polymorphic objects has effects that aren't at all obvious from a casual inspection of its components.

I should tell you that many, if not most, professional C++ programmers don't know about this method of making polymorphism safe and easy to use for the application programmer. Why, then, am I including it in a book for relatively inexperienced programmers?

Because I believe it is the best solution to the very serious problems caused by dynamic memory allocation when using polymorphism. For that reason, every serious C++ programmer should know this idiom and understand how to apply it to real-life problems.

At this point, Susan was ready to give this new idea a shot, as the following exchange indicates:

Susan: Okay, I feel I have followed you fairly well up to the point of the big thing you're going to do here with the polymorphic objects. I think that stuff is going to take some real thinking time. I hope it goes well.
Steve: I hope so too.
Assuming that I have impressed the importance of this technique on you, how does it work? The most elementary answer is that it involves creating a set of classes that work as a team to present the appearance of a simple object that has the desired polymorphic behavior. The user of the class (i.e., the application programmer) doesn't have to know about any of the details of this idiom; he or she merely defines an object of the single visible class and that object does what the user wants with the help of an object of another class. James Coplien calls these two kinds of classes envelope and letter, respectively, but I'm going to call them manager and worker. I think these names are easier to remember because the outside world sees only objects of the manager class, which take credit for everything done by the polymorphic object, even though most of the work is actually done by objects of the worker classes.

As usual, all of the intricacies of the implementation are the responsibility of the class designers (us). However, before we get into the details of how a polymorphic object works, let's see how it affects the way we use the StockItem class. Figure 10.21 shows how we will use the new StockItem class; note the lack of deletes and the fact that the variables in Figure 10.21 are StockItems rather than StockItem pointers.6

FIGURE 10.21. Safe polymorphism: Using operator >> and operator << with a polymorphic StockItem (code\polyioc.cpp)
#include <iostream>
#include <fstream>

#include "Vec.h"
#include "itemp.h"
using namespace std;

int main()
{
StockItem x;
StockItem y;

ifstream ShopInfo("shop22.in");

ShopInfo >> x;

ShopInfo >> y;

cout << "A StockItem: " << endl;
cout << x;

cout << endl;

cout << "A DatedStockItem: " << endl;
cout << y;

return 0;
}

I strongly recommend that you print out the files that contain the interface and the implementation of the polymorphic object version of StockItem, as well as the test program, to refer to as you go through this section of the chapter. Those files are itemp.h (StockItem interface in Figure 10.22), itempi.h (UndatedStockItem and DatedStockItem interfaces in Figure 10.23), itemp.cpp (UndatedStockItem and DatedStockItem implementation in Figure 10.24), and polyioc.cpp (test program in Figure 10.21).

You must be happy to see that we've eliminated the visible pointers in the new version of the example program, but how does it work? Let's start by looking at Figure 10.22, which shows the interface for the manager class StockItem. As we've discussed, this is the class of the objects that are visible to the user of the polymorphic object.

FIGURE 10.22. Safe polymorphism: The polymorphic object version of the StockItem interface (code\itemp.h)
// itemp.h

class StockItem
{
friend std::ostream& operator << (std::ostream& os,
const StockItem& Item);

friend std::istream& operator >> (std::istream& is, StockItem& Item);

public:
StockItem();
StockItem(const StockItem& Item);
StockItem& operator = (const StockItem& Item);
virtual ~StockItem();

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

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

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

virtual void Reorder(std::ostream& os);
virtual void FormattedDisplay(std::ostream& os);
virtual std::ostream& Write(std::ostream& os);

protected:
StockItem(int);

protected:
StockItem* m_Worker;
short m_Count;
};

Unlike the classes we've dealt with before, where the member functions deserved most of our attention, possibly the most interesting point about this new version of the StockItem class is its member variables, especially the variable named m_Worker. It's a pointer, which isn't all that strange; the question is, what type of pointer?

It's a pointer to a StockItem - that is, a pointer to the same type of object that we're defining! Assuming that is useful, is it even legal?

Using a Base class Pointer to Point to Derived class Objects

Yes, it is legal, because the compiler can figure out how to allocate storage for a pointer to any type whether or not it knows the full definition of that type. However, this doesn't answer the question of why we would want a pointer to a StockItem in our StockItem class in the first place. The answer is that, as we saw in the discussion of polymorphism earlier in this chapter, a pointer to a StockItem can actually point to an object of any class derived from StockItem via public inheritance. We're going to make use of this fact to implement the bulk of the functionality of our StockItem objects in the classes UndatedStockItem and DatedStockItem, which are derived from StockItem.

Susan didn't think this use of a pointer to refer to a worker object was very obvious. Here's the discussion we had about it.

Susan: Ugh. What is m_Worker? Where did it come from, and why is it suddenly so popular? Don't tell me it's a pointer. I want to know exactly what it does.
Steve: It points to the "worker" object that actually does the work for the StockItem, which is why it is called m_Worker.
In essence, we're renaming the old StockItem class to UndatedStockItem and creating a new StockItem manager class that will handle interaction with the application programmer. Objects of this new StockItem class will pass the actual class-specific operations to an object of the UndatedStockItem or DatedStockItem class as needed.

10.8. Implementing Safe Polymorphism

Now that we have an overview of the structure of the classes we're designing, Figure 10.23 shows the interfaces for the worker classes UndatedStockItem and DatedStockItem.
FIGURE 10.23. Safe polymorphism: The UndatedStockItem and DatedStockItem interfaces for the polymorphic version of StockItem (code\itempi.h)
class UndatedStockItem : public StockItem
{
public:
UndatedStockItem();

UndatedStockItem(std::string Name, short InStock,
short Price, short MinimumStock, short ReorderQuantity,
std::string Distributor, std::string UPC);

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

virtual void Reorder(std::ostream& os);
virtual void FormattedDisplay(std::ostream& os);
virtual std::ostream& Write(std::ostream& os);

protected:
short m_InStock;
short m_Price;
short m_MinimumStock;
short m_MinimumReorder;
std::string m_Name;
std::string m_Distributor;
std::string m_UPC;
};

class DatedStockItem : public UndatedStockItem
{
public:

DatedStockItem(std::string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
std::string Distributor, std::string UPC, std::string Expires);

virtual void Reorder(std::ostream& os);
virtual void FormattedDisplay(std::ostream& os);
virtual std::ostream& Write(std::ostream& os);

protected:
static std::string Today();

protected:
std::string m_Expires;
};

And Figure 10.24 shows the implementation of these classes.

FIGURE 10.24. Safe polymorphism: The implementation of the UndatedStockItem and DatedStockItem classes (code\itemp.cpp)
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include "itemp.h"
#include "itempi.h"
#include <dos.h>
using namespace std;

//friend functions of StockItem

ostream& operator << (ostream& os, const StockItem& Item)
{
return Item.m_Worker->Write(os);
}

istream& operator >> (istream& is, StockItem& Item)
{
string Expires;
string Name;
short InStock;
short Price;
short MinimumStock;
short MinimumReorder;
string Distributor;
string UPC;

getline(is,Expires);
getline(is,Name);
is >> InStock;
is >> MinimumStock;
is >> Price;
is >> MinimumReorder;
is.ignore();
getline(is,Distributor);
getline(is,UPC);

if (Expires == "0")
{
Item = StockItem(Name, InStock, Price, MinimumStock,
MinimumReorder, Distributor, UPC);
}
else
{
Item = StockItem(Name, InStock, Price, MinimumStock,
MinimumReorder, Distributor, UPC, Expires);
}

return is;

}


// StockItem member functions

StockItem::StockItem()
: m_Count(0), m_Worker(new UndatedStockItem)
{
m_Worker->m_Count = 1;
}

StockItem::StockItem(const StockItem& Item)
: m_Count(0), m_Worker(Item.m_Worker)
{
m_Worker->m_Count ++;
}

StockItem& StockItem::operator = (const StockItem& Item)
{
StockItem* temp = m_Worker;

m_Worker = Item.m_Worker;
m_Worker->m_Count ++;

temp->m_Count --;
if (temp->m_Count <= 0)
delete temp;

return *this;
}

StockItem::~StockItem()
{
if (m_Worker == 0)
return;

m_Worker->m_Count --;
if (m_Worker->m_Count <= 0)
delete m_Worker;
}

StockItem::StockItem(string Name, short InStock, // Undated
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC)
: m_Count(0),
m_Worker(new UndatedStockItem(Name, InStock, Price,
MinimumStock, MinimumReorder, Distributor, UPC))
{
m_Worker->m_Count = 1;
}

StockItem::StockItem(int)
: m_Worker(0)
{
}

StockItem::StockItem(string Name, short InStock, // Dated
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC, string Expires)
: m_Count(0),
m_Worker(new DatedStockItem(Name, InStock, Price,
MinimumStock, MinimumReorder, Distributor, UPC, Expires))
{
m_Worker->m_Count = 1;
}

bool StockItem::CheckUPC(string UPC)
{
return m_Worker->CheckUPC(UPC);
}

short StockItem::GetInventory()
{
return m_Worker->GetInventory();
}

void StockItem::DeductSaleFromInventory(short QuantitySold)
{
m_Worker->DeductSaleFromInventory(QuantitySold);
}

string StockItem::GetName()
{
return m_Worker->GetName();
}

ostream& StockItem::Write(ostream& os)
{
exit(1); // should never get here
return os;
}

void StockItem::Reorder(ostream& os)
{
m_Worker->Reorder(os);
}


void StockItem::FormattedDisplay(ostream& os)
{
m_Worker->FormattedDisplay(os);
}


// UndatedStockItem member functions

UndatedStockItem::UndatedStockItem()
: StockItem(1),
m_InStock(0),
m_Price(0),
m_MinimumStock(0),
m_MinimumReorder(0),
m_Name(),
m_Distributor(),
m_UPC()
{
}

UndatedStockItem::UndatedStockItem(string Name,
short InStock, short Price, short MinimumStock,
short MinimumReorder, string Distributor, string UPC)
: StockItem(1),
m_InStock(InStock),
m_Price(Price),
m_MinimumStock(MinimumStock),
m_MinimumReorder(MinimumReorder),
m_Name(Name),
m_Distributor(Distributor),
m_UPC(UPC)
{
}

void UndatedStockItem::FormattedDisplay(ostream& os)
{
os << "Name: ";
os << m_Name << endl;
os << "Number in stock: ";
os << m_InStock << endl;
os << "Price: ";
os << m_Price << endl;
os << "Minimum stock: ";
os << m_MinimumStock << endl;
os << "Reorder quantity: ";
os << m_MinimumReorder << endl;
os << "Distributor: ";
os << m_Distributor << endl;
os << "UPC: ";
os << m_UPC << endl;
os << endl;
}

ostream& UndatedStockItem::Write(ostream& os)
{
os << 0 << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_Price << endl;
os << m_MinimumStock << endl;
os << m_MinimumReorder << endl;
os << m_Distributor << endl;
os << m_UPC << endl;

return os;
}

void UndatedStockItem::Reorder(ostream& os)
{
short ReorderAmount;

if (m_InStock < m_MinimumStock)
{
ReorderAmount = m_MinimumStock-m_InStock;
if (ReorderAmount < m_MinimumReorder)
ReorderAmount = m_MinimumReorder;
os << "Reorder " << ReorderAmount;
os << " units of " << m_Name << " with UPC ";
os << m_UPC << " from " << m_Distributor << endl;
}
}

bool UndatedStockItem::CheckUPC(string UPC)
{
return (UPC == m_UPC);
}

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

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

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


// DatedStockItem member functions

DatedStockItem::DatedStockItem(string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC, string Expires)
: UndatedStockItem(Name,InStock,Price,MinimumStock,
MinimumReorder,Distributor,UPC),
m_Expires(Expires)
{
}

ostream& DatedStockItem::Write(ostream& os)
{
os << m_Expires << endl;
os << m_Name << endl;
os << m_InStock << endl;
os << m_Price << endl;
os << m_MinimumStock << endl;
os << m_MinimumReorder << endl;
os << m_Distributor << endl;
os << m_UPC << endl;

return os;
}

void DatedStockItem::FormattedDisplay(ostream& os)
{
os << "Expiration date: ";
os << m_Expires << endl;
os << "Name: ";
os << m_Name << endl;
os << "Number in stock: ";
os << m_InStock << endl;
os << "Price: ";
os << m_Price << endl;
os << "Minimum stock: ";
os << m_MinimumStock << endl;
os << "Reorder quantity: ";
os << m_MinimumReorder << endl;
os << "Distributor: ";
os << m_Distributor << endl;
os << "UPC: ";
os << m_UPC << endl;
os << endl;
}

string DatedStockItem::Today()
{
struct date d;
unsigned short year;
unsigned short day;
unsigned short month;
string TodaysDate;
stringstream FormatStream;

getdate(&d);
year = d.da_year;
day = d.da_day;
month = d.da_mon;

FormatStream << setfill(`0') << setw(4) << year <<
setw(2) << month << setw(2) << day;
FormatStream.seekg(0);
FormatStream >> TodaysDate;

return TodaysDate;
}

void DatedStockItem::Reorder(ostream& os)
{
if (m_Expires < Today())
{
os << "Return " << m_InStock << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " to " << m_Distributor << endl;
m_InStock = 0;
}

UndatedStockItem::Reorder(os);
}

Let's start our examination of this new StockItem class by looking at the implementation of operator << in Figure 10.25.
FIGURE 10.25. Safe polymorphism: The implementation of operator << for a polymorphic StockItem (from code\itemp.cpp)
ostream& operator << (ostream& os, const StockItem& Item)
{
return Item.m_Worker->Write(os);
}

At first glance, this isn't particularly complicated. It just calls a function named Write via the StockItem* member variable m_Worker and returns the result from Write to its caller. But what does that m_Worker pointer point to?

This is the key to the implementation of polymorphic objects. The pointer m_Worker points to either a DatedStockItem or an UndatedStockItem depending on whether the object was created with or without an expiration date, respectively. Since the type of object that m_Worker points to is determined during program execution rather than when the program is compiled, the actual version of the Write function called by operator << varies accordingly, as it does with any type of polymorphism. The difference between this version of StockItem and the one described earlier in this chapter is that the pointer is used only in the implementation of StockItem rather than being accessible to the user of the class. This allows us to prevent the plague of memory allocation errors associated with pointer manipulation in the application program.

Susan had a question about the syntax of the call to the Write function.

Susan: What does the -> mean in that line?
Steve: It means to call the function on the right of the -> for the object pointed to by the pointer on the left of the ->. It's exactly like the . operator, except that operator has an object on its left instead of a pointer.
Before we get into the details of creating an object of the type defined by this new version of StockItem, we'll look at a couple of diagrams of the StockItem variables from the example program in Figure 10.21 to see exactly how this "internal polymorphism" works. First, Figure 10.26 shows a possible layout of the StockItem object, x, which is the argument to operator << in the statement cout << x;.

Let's trace the execution of the statement return Item.m_Worker>Write(os); from operator << (Figure 10.25 on page 710) when it is executed to display the value of the StockItem x (as a result of the statement cout << x; in the example program in Figure 10.21 on page 696). In step 1, the pointer m_Worker is followed to location 12321000, the address of the beginning of the UndatedStockItem worker object that will handle the operations of the StockItem manager class object. This location contains the address of the vtable for the worker object. In this case, the worker object is an UndatedStockItem object, so the vtable is the one for the UndatedStockItem class.

In our diagram that vtable is at location 12380000, so in step 2 we follow the vtable pointer to find the address of the Write function. The diagram makes the assumption that the Write function is the second virtual function defined in the StockItem class, so in step 3 we fetch the contents of the second entry in the vtable, which is 12390900. That is the address of the Write function that will be executed here.

Figure 10.27 shows a possible layout of the StockItem object y that is the argument to operator << in the statement cout << y;.

Susan had a number of questions about Figure 10.27.

Susan: What in Figure 10.27 is the StockItem? I can't tell exactly what it is supposed to be, just the vtable address, m_Worker, and m_Count? That's it, huh?
Steve: Yes.
FIGURE 10.26. Safe polymorphism: A polymorphic StockItem object with no date
FIGURE 10.27. Safe polymorphism: A polymorphic StockItem object with a date

Susan: What are all those member functions in step 3 supposed to be? I don't know where they're coming from.
Steve: They're from the class interface for the new version of StockItem, which is listed in Figure 10.22.
Susan: Why is there an UndatedStockItem listed along the side of the DatedStockItem thingy?
Steve: Since DatedStockItem is derived from UndatedStockItem, every DatedStockItem has an UndatedStockItem in it, just as every UndatedStockItem has a StockItem in it because UndatedStockItem is derived from StockItem.
Susan: Where is the UndatedStockItem now?
Steve: The only UndatedStockItem in Figure 10.27 is the base class part of the DatedStockItem worker object.
Susan: I still can't see how these objects look.
Steve: I think we need a diagram here. Take a look at Figure 10.28 and let me know if that helps.
Susan: Yes, it does.
Now that we've cleared that up, let's trace how the line return Item.m_Worker>Write(os); is executed to display the value of the StockItem y (as a result of the statement cout << y; in the example program in Figure 10.21). In step 1, the pointer m_Worker is followed to location 22321000, the address of the worker object that will handle the operations of the StockItem manager class object. This location contains the address of the vtable for the worker object. In this case, the worker object is a DatedStockItem object, so the vtable is the one for the DatedStockItem class. In our diagram, that vtable is at location 22380000, so in step 2 we follow the vtable pointer to find the address of the Write function. As before, the diagram makes the assumption that the Write function is the second virtual function defined in the StockItem class, so in step 3 we fetch the contents of the second entry in the vtable, which is 22390900. That is the address of the Write function that will be executed in this case.
FIGURE 10.28. A simplified version of the structure of a DatedStockItem object

10.9. Reimplementing the Standard Member Functions for the New Version of StockItem

We have seen how a polymorphic object works once it is set up, so let's continue our examination of the StockItem class by looking at the "standard" member functions; that is, the ones that are necessary to make it a concrete data type. As you may remember, these are the default constructor, the copy constructor, the assignment operator (operator =), and the destructor.

It may occur to you to wonder why we have to rewrite all these functions; what's wrong with the ones we've already written for StockItem? The answer is that these functions create, copy, and destroy objects of a given class. Now that we have changed the way we want to use the StockItem class and the way it works, we have to rewrite these functions to do the right thing in the new situation.

Susan wanted to make sure she understood where we were going with this:

Susan: Okay, then this is the new StockItem, not the old one. This is your manager StockItem that tells the worker what to do; the DatedStockItem is the worker, right?
Steve: Yes, this is the new type of StockItem. By the way, besides a DatedStockItem, an UndatedStockItem can also be a worker.
Let's start with the default constructor for this new StockItem class, shown in Figure 10.29.
FIGURE 10.29. Safe polymorphism: The default constructor for the polymorphic StockItem class (from code\itemp.cpp)
StockItem::StockItem()
: m_Count(0), m_Worker(new UndatedStockItem)
{
m_Worker->m_Count = 1;
}

The first member initialization expression in this function merely initializes m_Count to 0. We won't actually be using this variable in a StockItem object, but I don't like leaving variables uninitialized.

The second member initialization expression, however, looks a bit odd. Why are we creating an UndatedStockItem object here when we have no data to put in it? Because we need some worker object to perform the work of a default-constructed StockItem. For example, if the user asks for the contents of the StockItem to be displayed on the screen with labels (by calling FormattedDisplay), we want the default values displayed with the appropriate labels, which can only be done by an object that has a working FormattedDisplay function. The StockItem class itself doesn't have any data except for the StockItem* and the m_Count variable (which we'll get to later). Therefore, all of the functionality of a StockItem has to be handed off to the worker object, which in this case is the newly created UndatedStockItem.

A default-constructed StockItem might look like (Figure 10.30). Susan didn't think the need for a valid object as the worker object for a default-constructed manager object was intuitively obvious.

Susan: I don't get why you say that we can only call the FormattedDisplay function with an object that has a working version of that function. That doesn't mean anything to me.
Steve: Well, what would happen if we called FormattedDisplay via a pointer that didn't point to anything? It wouldn't work, that's for sure.
In particular, any attempt by the user to call a virtual function through a pointer to a nonexistent object will fail, because there won't be a valid vtable address in that missing object. The most likely result will be a crash when the code tries to use a vtable entry that contains random garbage.
I still haven't explained exactly what the member initialization expression, m_Worker(new UndatedStockItem), actually does. On the surface, it's pretty simple: it creates a new UndatedStockItem object via the default constructor of that class and uses the address of the resulting object to initialize m_Worker.

However, there are some tricks in the implementation of the constructor of a worker class, and now is the time to see how such a constructor actually works. Figure 10.31 shows the code for the default constructor for the UndatedStockItem class.

FIGURE 10.30. Safe polymorphism: A default-constructed polymorphic StockItem object
FIGURE 10.31. Safe polymorphism: The default constructor for the UndatedStockItem class (from code\itemp.cpp)
UndatedStockItem::UndatedStockItem()
: StockItem(1),
m_InStock(0),
m_Price(0),
m_MinimumStock(0),
m_MinimumReorder(0),
m_Name(),
m_Distributor(),
m_UPC()
{
}

Most of the code in this constructor is standard; all we're doing is initializing the values of the member variables to reasonable default values. But there's something a bit unusual about that base class initializer, StockItem(1). Before we get into exactly what the argument 1 means, it's important to understand why we must specify a base class initializer here.

As we saw in Chapter 9 in the discussion of Figure 9.33 on page 639, we use a base class initializer when we want to specify which base class constructor will initialize the base class part of a derived class object. If we don't specify any particular base class initializer, the base class part will be initialized with the default constructor for that base class. In the example in Chapter 9, we needed to call a specific base class constructor to fill in the fields in the base class part of the DatedStockItem object, which should seem reasonable enough. But that can't be the reason we need to specify a base class constructor here, because the StockItem object doesn't have any data fields that need to be initialized in our UndatedStockItem constructor. So why can't we just let the compiler use the default constructor for the base class part of an UndatedStockItem?

10.10. Avoiding an Infinite Regress During Construction

We can't do that because the default constructor for StockItem calls the default constructor for UndatedStockItem; that is, the function that we're examining right now. Therefore, if we allow the StockItem default constructor to initialize the StockItem part of an UndatedStockItem, that default constructor will call our UndatedStockItem default constructor again, which will call the StockItem default constructor again, and the program will eventually use up all the stack space and die.

To avoid this problem, we have to make a special constructor for StockItem that doesn't create an UndatedStockItem object and therefore avoids an indefinitely long chain of constructor calls. As no one outside the implementation of the StockItem polymorphic object knows anything about classes in this idiom other than StockItem, they don't need to call this constructor. As a result, we can make it protected.

Susan didn't see why we need a special constructor in this case, so I explained it to her some more:

Susan: So, how many default constructors are you going to need for StockItem? How do you know when or which one is going to be used? This is confusing.
Steve: There is only one default constructor for each class. In this case, StockItem has one, DatedStockItem has one, and UndatedStockItem has one. The question is how to prevent the StockItem default constructor from being called from the UndatedStockItem one, which would be a big booboo, since the UndatedStockItem default constructor was called from the StockItem default constructor. This would be like having two mirrors facing one another, where you see endless reflections going off into the distance.
Okay, so how do we declare a special constructor for this purpose? As was shown in Figure 10.22, all we have to do is to put the line StockItem(int); in a protected section of the class definition. The implementation of this function is shown in Figure 10.32.
FIGURE 10.32. Safe polymorphism: Implementing a special protected constructor for StockItem (from code\itemp.cpp)
StockItem::StockItem(int)
: m_Worker(0)
{
}

How can a function that doesn't have any code inside its { } be complicated? In fact, this apparently simple function raises three questions. First, why do we need any entries in the argument list when the function doesn't use any arguments? Second, why does the list contain just a type and no argument name instead of a name and type for each argument, as we had in the past? And third, why are we initializing m_Worker to the value 0? We'll examine the first two of these questions now and put off the third until we discuss the destructor for the StockItem class.

The answers to the first two questions are related. The reason we don't need to specify a name for the argument is that we aren't going to use the argument in the function. The only reason to specify an argument list here is to make use of the function overloading mechanism, which allows the compiler to distinguish between functions with the same name but different argument types. In this case, even though all the constructors for the StockItem class have the same name - StockItem::StockItem - the compiler can tell them apart so long as they have different argument types. Therefore, we're supplying an argument we don't need to allow the compiler to pick this function when we want to use it. Here, when we call the function from the worker object's constructor, we supply the value 1, which will be ignored in the function itself but will tell the compiler that we want to call this constructor rather than any of the other constructors.

Susan wanted to know exactly where the 1 was going to come from and how I decided to use that value in the first place:

Susan: Where are you supplying the 1 value?
Steve: In the base class initializer in the default constructor for UndatedStockItem. This is needed to prevent the infinite regress mentioned just above.
Susan: But why 1? Why not any other number, like 0?
Steve: Actually, the value 1 is fairly arbitrary; any number would work... except 0. In general, it's a good idea to avoid using 0 where you just need a number but don't care which one, because 0 is a "magic number" in C++; it's a legal value for any type of built-in variable as well as any type of pointer. This "multiple identity" of 0 can be a bountiful source of confusion and error in C++, which it's best to avoid whenever possible.
So that explains why we need an argument that we're not using. But why leave the name of the argument out of the function header?

There's nothing to stop us from giving the argument a name even though we aren't going to use it in the constructor. It's better not to do this, however, to avoid confusing both the compiler and the next programmer who looks at this function. The compiler may give us a warning message if we don't use an argument we've declared, while the next programmer to look at this function may think we forgot to use that argument. We can solve both of these problems by not giving it a name, which makes it clear that we weren't planning to use it in the first place.

So now we've followed the chain of events down to the initialization of the base class part of the UndatedStockItem object that was created as the worker object inside the default constructor for StockItem. The rest of Figure 10.31 is pretty simple. It merely initializes the data for the UndatedStockItem class itself. When that's done, we're ready to execute the lone statement inside the {} in Figure 10.29 on page 716, which is m_Worker>m_Count = 1;. Clearly, this sets the value of m_Count in the newly created UndatedStockItem object to 1, but what might not be as clear is why we need to do this.

10.11. Reference Counting

The reason we have to set m_Count to 1 in the UndatedStockItem variable pointed to by m_Worker is that we're going to keep track of the number of StockItems that are using that UndatedStockItem worker object, rather than copy the worker object every time we copy a StockItem that points to it, as we did with our string class. This approach to sharing an object is called reference counting.

The general idea of reference counting is fairly simple, as most great ideas are (after you understand them, at least). It's inefficient to copy a lot of data whenever we set one variable to the same value as that of another; copying a pointer to the data is much easier. Even so, we have to consider how we will know when we can delete the data being pointed to, which will be when no one needs the data anymore. If we don't take care of this requirement, we'll have a serious problem with memory management. When one of the StockItem objects that refers to the worker object goes out of scope and is destroyed, the destructor can do either of the following, neither of which is correct:

1. free the memory where the data is kept, via delete,
2. fail to free the memory.
The first of these is incorrect because there may be other StockItems that still want to use the data in question, and they will now be referring to memory that is no longer allocated to that data. Therefore, at any time the data in that area of memory may be overwritten by new data. The second is also incorrect because when the last StockItem that was using the shared data goes away, a memory leak results. In other words, although the data formerly used by the StockItem can no longer be accessed, the memory it occupies cannot be used for other purposes because it has not been released back to the system via operator delete.7

The correct way to share data in such a situation is to write the constructor(s), destructor, and assignment operator to keep track of the number of objects using a particular set of data and, when that set of data has no more users, to free the memory it occupies.8 Let's see how reference counting works with our StockItem class.

10.12. Sharing a Worker Object

Suppose that we have the example program in Figure 10.33 (which is contained in the file refcnt1.cpp if you want to print it out).
FIGURE 10.33. Safe polymorphism: An example program for reference-counting with StockItems (code\refcnt1.cpp)
#include <iostream>
#include <string>
#include "itemp.h"
using namespace std;

int main()
{
StockItem item1("cups",32,129,10,5,"Bob's Dist.",
"2895657951"); // create an undated object

StockItem item2("Hot Chicken",48,158,15,12,"Joe's Dist.",
"987654321", "19960824"); // create a dated object

StockItem item3 = item1; // copy constructor
item1 = item2; // assignment operator

item1.FormattedDisplay(cout); // display an object with labels

return 0;
}

This program doesn't do anything useful, except to illustrate the constructors and assignment operator via the following steps:
1. First, it creates two StockItems, item1 and item2 via the "normal" constructors for undated and dated items, respectively.
2. Then it creates another StockItem, item3, with the same value as that of item1, via the copy constructor.
3. Then it assigns the value of item2 to item1 via operator =.
4. Finally, it displays the resulting value of item1.
The statement StockItem item1("cups", 32, 129, 10, 5, "Bob's Dist.", "2895657951"); calls the constructor illustrated in Figure 10.34 to create a StockItem whose m_Worker points to an UndatedStockItem, because the arguments "cups", 32, 129, 10, 5, "Bob's Dist.", and "2895657951" match the argument list for that constructor.
FIGURE 10.34. Safe polymorphism: A normal constructor to create a StockItem without a date (from code\itemp.cpp)
StockItem::StockItem(string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC)
: m_Count(0),
m_Worker(new UndatedStockItem(Name, InStock, Price,
MinimumStock, MinimumReorder, Distributor, UPC))
{
m_Worker->m_Count = 1;
}

Susan had some comments about this normal constructor for the new version of the StockItem class as well as about the other normal constructor (in Figure 10.36 on page 727):
Susan: I don't get how this can be a normal constructor if it has m_Worker in it. The normal ones are the ones you wrote before this. I am confused. Same thing with Figure 10.36; these are not normal constructors for StockItem.
Steve: The implementation of the constructor doesn't determine whether it is "normal". A "normal" constructor is one that creates an object with specified data in it, as opposed to a default constructor (Figure 10.31 on page 719) or a copy constructor (Figure 10.38 on page 728).
Immediately after the execution of the constructor call StockItem item1("cups", 32, 129, 10, 5, "Bob's Dist.", "2895657951");, the newly constructed StockItem object and its UndatedStockItem worker object look something like the diagram in Figure 10.35.9

The next line in Figure 10.33 on page 724, StockItem item2("Hot Chicken", 48, 15, 12, "Joe's Dist.", "987654321", "19960824");, calls the constructor shown in Figure 10.36, which creates a StockItem whose m_Worker member variable points to a DatedStockItem. As you can see, this is almost identical to the previous constructor, except of course that it creates a DatedStockItem as the worker object rather than an UndatedStockItem.

FIGURE 10.35. Safe polymorphism: A polymorphic StockItem object with an UndatedStockItem worker
After the statement StockItem item2("Hot Chicken", 48, 158, 15, 12, "Joe's Dist.", "987654321", "19960824"); is executed, the newly constructed StockItem object and its DatedStockItem worker object looks something like the diagram in Figure 10.37.
FIGURE 10.36. Safe polymorphism: A normal constructor that constructs a StockItem having a date (from code\itemp.cpp)
StockItem::StockItem(string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC, string Expires)
: m_Count(0),
m_Worker(new DatedStockItem(Name, InStock, Price,
MinimumStock, MinimumReorder, Distributor, UPC, Expires))
{
m_Worker->m_Count = 1;
}

FIGURE 10.37. Safe polymorphism: A polymorphic StockItem object with a DatedStockItem worker
Now let's take a look at what happens when we execute the next statement in Figure 10.33 on page 724, StockItem item3 = item1;. Since we are creating a new StockItem with the same contents as an existing StockItem, this statement calls the copy constructor, which is shown in Figure 10.38.
FIGURE 10.38. Safe polymorphism: The copy constructor for StockItem (from code\itemp.cpp)
StockItem::StockItem(const StockItem& Item)
: m_Count(0), m_Worker(Item.m_Worker)
{
m_Worker->m_Count ++;
}

Susan wanted to know why we would want to create a new StockItem with the same contents as an existing one.
Susan: Why would you want to create a new StockItem just like an old one? Why not just use the old one again?
Steve: The most common use for the copy constructor is when we pass an argument by value or return a value from a function. In either of these cases, the copy constructor is used to make a new object with the same contents as an existing one.
Susan: I still don't see why we need to make copies rather than using the original objects.
Steve: In the case of a value argument, this is necessary because we don't want to change the value of the caller's variable if we change the local variable.
In the case of a return value, it's necessary to make a copy because an object that is created inside the called function will cease to exist at the end of the function, before the calling function can use it. Therefore, when we return an object by value we are actually asking the compiler to make a copy of the object; the copy is guaranteed to last long enough for the calling function to use it.
This copy constructor uses the pointer from the existing StockItem object (Item.m_Worker) to initialize the newly created StockItem object's pointer, m_Worker, so that the new and existing StockItem objects will share a worker object. Then we initialize the value of m_Count in the new object to 0 so that it has a known value. Finally, we increment the m_Count variable in the worker object because it has one more user than it had before.

After this operation, the variables item1 and item3, together with their shared worker object, look something like Figure 10.39.

FIGURE 10.39. Safe polymorphism: Two polymorphic StockItem objects sharing the same UndatedStockItem worker object
Why is it all right to share data in this situation? Because we have the m_Count variable to keep track of the number of users of the worker object so that our StockItem destructor will know when it is time to delete that object. This should be abundantly clear when we get to the end of the program and see what happens when the destructor is called for the StockItem variables.

Now let's continue by looking at the next statement in Figure 10.33 on page 724: item1 = item2;. As you may have figured out already, this is actually a call to the assignment operator (operator =) for the StockItem class.

In case that wasn't obvious to you, don't despair; it wasn't to Susan either:

Susan: No, I didn't figure out that this is a call to the assignment operator. It seems to me that you are setting item2 to item1. I see that operator = has to be called to do this, but I don't see how the program is intentionally calling it.
Steve: Whenever we assign the value of one object to another existing object of the same class, we are calling the assignment operator of that class. That's what "=" means for objects.
The code for that operator is shown in Figure 10.40.
FIGURE 10.40. Safe polymorphism: The assignment operator (operator =) for StockItem (from code\itemp.cpp)
StockItem& StockItem::operator = (const StockItem& Item)
{
StockItem* temp = m_Worker;

m_Worker = Item.m_Worker;
m_Worker->m_Count ++;

temp->m_Count --;
if (temp->m_Count <= 0)
delete temp;

return *this;
}

This function starts out with the line StockItem* temp = m_Worker, which makes a copy of the pointer to the worker object that we are currently using. We'll see why we have to do this shortly.

The next statement in the code for operator =, m_Worker = Item.m_Worker;, copies the worker object pointer from the object we are copying from to our object. Now item1 and item2 are sharing a worker object, so they are effectively the same. This also means that worker object has one more manager, so we have to increase that worker object's manager count (in this case to 2), which we do in the next line, m_Worker->m_Count++.

Now we have to correct the count of managers for our previous worker object, which is why we saved its address in the variable temp. Of course, that worker object now has one less manager object. Therefore, we have to execute the statement temp->m_Count --; to correct that count.

If you look at Figure 10.39, you'll see that the previous value of that variable was 2, so it is now 1, meaning that there is one StockItem that is still using that worker object. Therefore, the condition in the next line, if (temp>m_Count <= 0), is false, which means that the controlled statement of the if statement - delete temp; - is not executed. This is correct because as long as there is at least one user of the worker object, it cannot be deleted.

Finally, as is standard with assignment operators, we return to the calling function by the statement return *this;, which returns the object to which we have assigned a new value.

When all this has been done, item1 and item2 will share a DatedStockItem, while item3 will have its own UndatedStockItem. The manager variables item1 and item2, together with their shared worker object, look something like Figure 10.41, and item3 looks pretty much as shown in Figure 10.42.

You should note that the assignment statement item1 = item2; has effectively changed the type of item1 from UndatedStockItem to DatedStockItem. This is one of the benefits of using polymorphic objects; the effective type of an object can vary not only when it is created, but at any time thereafter. Therefore, we don't have to be locked in to a particular type when we create an object, but can adjust the type as necessary according to circumstances. By the way, this ability to change the effective type of an object at run time also solves the slicing problem referred to in Chapter 9, where we assigned an object of a derived class to a base class object with the result that the extra fields from the derived class object were lost.

FIGURE 10.41. Safe polymorphism: Two polymorphic StockItem objects sharing the same DatedStockItem worker object
Susan and I had quite a discussion about the implementation of this version of operator =:
Susan: If this is an assignment operator, then how come it has the code for reference counting in it?
Steve: It needs code for reference counting because it has to keep track of the number of users of its former worker object and its new worker object. First, it has to make a copy of the pointer to its worker object, so it can adjust the number of managers for that worker. The following line does that:
StockItem* temp = m_Worker;
Next, it has to copy the pointer to the worker object from "Item", like so:

m_Worker = Item.m_Worker;
Susan: Wait a minute. I don't understand what this part does.
Steve: It makes the current object (the one pointed to by this) share a "worker" object with the manager object on the right of the =, which we refer to here as Item.
Susan: Okay.
Steve: Next, it has to increment the number of users of the worker object from Item, since that worker object is now being used by "our" object (i.e., the one pointed to by this). That's taken care of by the line:

m_Worker->m_Count ++;
Next, we take care of adjusting the manager count for our former worker object, which is handled by the line:

temp->m_Count --;

If there aren't any more users of that worker object, then it can (and indeed must) be deleted. That's the purpose of the lines:

if (temp->m_Count <= 0)
delete temp;

Susan: So, do we have pointers pointing to pointers here?
Steve: Not exactly: We have one object containing a pointer to another object.

FIGURE 10.42. Safe polymorphism: A polymorphic StockItem object

The Order of Destructor Calls

Now let's take a look at what happens when the StockItem objects are automatically destroyed at the end of the main program. As the C++ language specifies for the destruction of auto variables, the last to be created will be the first to be destroyed. Thus, item3 will be destroyed first, followed by item2, and finally item1. Figure 10.43 shows the code for the destructor for StockItem.
FIGURE 10.43. Safe polymorphism: The destructor for the StockItem class (from code\itemp.cpp)
StockItem::~StockItem()
{
if (m_Worker == 0)
return;

m_Worker->m_Count --;
if (m_Worker->m_Count <= 0)
delete m_Worker;
}

Before we get into the details of this code, I should mention that whenever any object is destroyed, all of its constituent elements that have destructors are automatically destroyed as well. In the case of a StockItem, the string variables are automatically destroyed during the destruction of the StockItem.

Susan had some questions about the destructor. Here's the first installment:

Susan: Why are you doing reference counting again in the destructor? Is that where it belongs? So, you have to reference-count twice, once for when something is added and again, with different code for when something is subtracted?
Steve: Close; actually, it's a bit more general. Every time a worker object acquires another manager, we have to increment the worker's reference count, and every time it loses one of its managers, we have to decrement the worker's reference count. That way, when the count gets to 0, we know there aren't any more managers for that worker, and we can therefore use delete to destroy the worker object.
Actually, it should be easy to remember this by analogy with a real employment situation: if you're a worker and you no longer have any managers, you're likely to be laid off pretty soon!
Susan: So with this reference counting, the whole point is to know when to use delete?
Steve: Yes.
Susan: How does the delete operator automatically call the destructor for that variable?
Steve: The compiler does that for you. Calling delete for a variable that has a destructor always calls the destructor for that variable.
The first if statement in the destructor, if (m_Worker == 0), provides a clue as to why our special StockItem constructor (Figure 10.32 on page 721) had to set the m_Worker variable to 0. We'll see exactly how that comes into play shortly. For now, we know that the value of m_Worker in item3 is not 0, as it points to an UndatedStockItem (see Figure 10.42 on page 735), so the condition in the first if statement is false. So we move to the next statement, m_Worker>m_Count;. Since, according to Figure 10.42, the value of m_Count in the UndatedStockItem to which m_Worker points is 1, this statement reduces the value of that variable to 0, making the condition in the statement if (m_Worker>m_Count <= 0) true. This means that the controlled statement of the if statement, delete m_Worker;, is executed. Since the value of m_Count is 0, no other StockItem variables are currently using the UndatedStockItem pointed to by m_Worker. Therefore, we want that UndatedStockItem to go away so that its memory can be reclaimed. We use the delete operator to accomplish that goal.

Susan had a question about why I used <= as the condition in the if statement.

Susan: Why did you say <= 0? How could the count ever be less than 0?
Steve: That's a very good question. If the program is working correctly, it can't. This is a case of "defensive programming": I wanted to make sure that if, by some error, the count got below 0, the program wouldn't hang onto the memory for the worker object forever. In a production program, it would probably be a good idea to record such an "impossible" condition somewhere so the maintenance programmer could take a look at it.
This is not quite as simple as it may seem, however, because before the memory used by an UndatedStockItem can be reclaimed, the destructor for that UndatedStockItem must be called to allow all of its constituent parts (especially the string variables it contains) to expire properly. As I've mentioned, when we call the delete operator for a variable that has a destructor, it automatically calls the destructor for that variable. Therefore, the next function called is the destructor for the UndatedStockItem being deleted.

Before going into the details of this function, I should explain why it is called in the first place. Remember, we're using StockItem*s to refer to either DatedStockItem or UndatedStockItem objects. How does the compiler know to call the right destructor?

Just as it does with other functions in the same situation. We have to make the destructor virtual so that the correct destructor will be called, regardless of the type of the pointer through which the object is accessed. This answers the earlier question of why we had to define a destructor and declare it to be virtual in Figure 10.13. As a general rule, destructors should be virtual if there are any other virtual functions in the class, as we have to make sure that the right destructor will be called via a base class pointer. Otherwise, data elements that are defined in the derived class won't be destroyed properly, possibly resulting in memory leaks or other undesirable effects.

At this point, Susan had some questions on virtual destructors and the nature of reality (as it applies to C++, at least):

Susan: How are destructors virtual? Do they have a vtable?
Steve: They're in the vtable if they're declared to be virtual, as the one in the final version of StockItem is.
Susan: With all this virtual stuff going on, what is really real? What is the driving force behind all this? It seems like this is a fun house of smoke and mirrors, and I can't tell any more what is really in control.
Steve: The StockItem object is the "manager", who takes credit for work done by a "worker" object; the worker is either a DatedStockItem or an UndatedStockItem. Hopefully, the rest of the discussion will clarify this.
We don't have to write a destructor for UndatedStockItem because the compiler-generated one does the job for us. But what exactly does that compiler-generated destructor do?

It calls the destructor for every member variable in the class that has a destructor. This is necessary to make sure that those member variables are properly cleaned up after their scope expires when the object they're in goes away. In addition, just as the constructor for a derived class always calls a constructor for its embedded base class object, so a destructor for a derived class always calls the destructor for the base class part of the derived class object. There are two differences between these situations, however:

1. The constructor for the base class part of the object is called before any of the code in the derived class constructor is executed; the base class destructor is called after the code in the derived class destructor is executed. Here, of course, this distinction is irrelevant because we haven't written any code in the derived class destructor.
2. There is only one destructor for a given class.
This second difference between constructors and destructors means that we can't use a trick similar to the "special base class constructor" that we used to prevent the base class constructor from calling the derived class constructor again. Instead, we have to arrange a way for the base class destructor to determine whether it's being asked to destroy a "real" base class object (a StockItem) or the embedded base class part of a derived class object (the StockItem base class part of an UndatedStockItem or DatedStockItem). In the latter case, the destructor should exit immediately, since the StockItem base class part of either of those classes contains nothing that needs special handling by the destructor. The special constructor called by the UndatedStockItem constructor to initialize its StockItem part (Figure 10.32 on page 721) works with the first if statement in the StockItem destructor (Figure 10.43 on page 735) to solve this problem. The special StockItem constructor sets m_Worker to 0 (which cannot be the address of any object) during the initialization of the base class part of an UndatedStockItem. When the destructor for StockItem is executed, a 0 value for m_Worker is the indicator of a StockItem that is the base class part of a derived class object. This allows the StockItem destructor to distinguish between a real StockItem and the base class part of an object of a class derived from StockItem by examining the value of m_Worker and bailing out immediately if it is the reserved value of 0.

Tracing the Destruction of Our Polymorphic Objects

Now it's time to go into detail about what happens when item3, item2, and item1 are destroyed. They go away in that order because the last object to be constructed on the stack in a given scope is the first to be destroyed, as you might expect when dealing with stacks.

Figure 10.44 is another listing of the destructor for the StockItem class (StockItem::~StockItem()), for reference as we trace its execution.

FIGURE 10.44. Safe polymorphism: The destructor for the StockItem class (from code\itemp.cpp)
StockItem::~StockItem()
{
if (m_Worker == 0)
return;

m_Worker->m_Count --;
if (m_Worker->m_Count <= 0)
delete m_Worker;
}

At the end of the main program (Figure 10.42 on page 735), item3, which was the last StockItem to be created, is the first to go out of scope. At that point, the StockItem destructor is automatically invoked to clean up. Since the value of m_Worker in item3 isn't 0, the statement controlled by the first if statement isn't executed. Next, we execute the statement m_Worker>m_Count;, which reduces the value of the variable m_Count in the UndatedStockItem pointed to by m_Worker to 0. Since this makes that variable 0, the condition in the second if statement is true, so its controlled statement, delete m_Worker;, is executed. As we've seen, this eliminates the object pointed to by m_Worker, calling the UndatedStockItem destructor in the process.

As before, calling this destructor does nothing other than destroy the member variable that has a destructor (namely, the m_Expires member variable, which is a string), followed by the mandatory call to the base class destructor.

At that point, the first if statement in StockItem::~StockItem comes into play along with its controlled statement. These two statements are

if (m_Worker == 0)

return;

Remember the special base class constructor StockItem(int) (Figure 10.32 on page 721)? That constructor, which is called from our UndatedStockItem default and normal constructors, initializes m_Worker to 0. Thus, we know that m_Worker will be 0 for any object that is actually an UndatedStockItem or a DatedStockItem (as distinct from one that is actually a StockItem), because all of the constructors for DatedStockItem call one of those two constructors for UndatedStockItem to initialize their UndatedStockItem base class part. Therefore, this if statement will be true for the base class part of all UndatedStockItem and DatedStockItem objects. Since the current object being destroyed is in fact an UndatedStockItem object, the if is true, and so the destructor exits immediately, ending the destruction of the UndatedStockItem object. Then the destructor for item3 finishes by freeing the storage associated with that object.

Next, the StockItem destructor is called for item2 (Figure 10.41 on page 733). Since m_Worker is not 0, the condition in the first if in Figure 10.44 on page 740 is false, so we proceed to the next statement, m_Worker>m_Count;. As you can see by looking back at Figure 10.41, the previous value of that variable was 2, so it is now 1. As a result, the condition in the next if statement, if (m_Worker>m_Count <= 0), is also false. Thus, the controlled statement that uses delete to get rid of the DatedStockItem pointed to by m_Worker is not executed. Then the destructor for item2 finishes by freeing the storage associated with that object.

Finally, item1 dies at the end of its scope. When it does, the StockItem destructor is called to clean up after it. As before, m_Worker isn't 0, so the controlled statement of the first if statement in Figure 10.44 on page 740 isn't executed. Next, we execute the statement m_Worker>m_Count;, which reduces the value of the variable m_Count in the DatedStockItem pointed to by m_Worker to 0. This time, the condition in the next if statement is true, so its controlled statement, delete m_Worker;, is executed. We've seen that this eliminates the object pointed to by m_Worker, calling the appropriate destructor for the worker object, which in this case is DatedStockItem::~DatedStockItem. As with the UndatedStockItem destructor, the compiler-generated destructor for DatedStockItem calls all the destructors for the member variables and base class part, which in this case is an UndatedStockItem. Therefore, we don't have to write this destructor ourselves.

Lastly, the call to the StockItem destructor occurs exactly as it did in the destruction of item3, except that there is an additional step because the base class of DatedStockItem is UndatedStockItem, and the destructor for that class in turn calls the destructor for its base class part, which is a StockItem. Once we get to the destructor for StockItem, the value of m_Worker is 0, so that destructor simply returns to the destructor for UndatedStockItem, which returns to the destructor for DatedStockItem. Then the destructor for item1 finishes by freeing the storage associated with that object.

If you find the above explanation clear, congratulations. Susan didn't:

Susan: How do you know that the m_Worker can never be 0 in a real StockItem and is always 0 in a StockItem that is a base class part of a derived class object?
Steve: Because I set m_Worker to 0 in the special StockItem constructor called from the base class initializer in the default and normal constructors for UndatedStockItem. Therefore, that special constructor is always used to set up the base class part of an UndatedStockItem, and since DatedStockItem derives from UndatedStockItem, the same will be true of a DatedStockItem.

10.13. Why We Need m_Count in StockItem

Now it's time to clear up a point we've glossed over so far. We have already seen that the member initialization expression m_Count(0), present in the constructors for the StockItem object, is there just to make sure we don't have an uninitialized variable in the StockItem object - even though we won't be using m_Count in a StockItem object. While this is true as far as it goes, it doesn't answer the question of why we need this variable at all if we're not using it in the StockItem class. The clue to the answer is that we are using that member variable in the object pointed to by m_Worker (i.e., m_Worker->m_Count). But why don't we just add the m_Count variable when we create the UndatedStockItem class rather than carry along extra baggage in the StockItem class?

The answer is that m_Worker is not an UndatedStockItem* but a StockItem*. Remember, the compiler doesn't know the actual type of the object being pointed to at compile time; all it knows is the declared type of the pointer, which in this case is StockItem*. It must therefore use that declared type to determine what operations are permissible through that pointer. Hence, if there's no m_Count variable in a StockItem, the compiler won't let us refer to that variable through a StockItem*.

Susan wasn't sure of what I was trying to say here:

Susan: What do you mean, if there were no m_Count variable in a StockItem, we wouldn't be able to access it through a StockItem pointer?
Steve: The only member variables and member functions that you can access through a pointer are ones that exist in the class that pointer is declared as pointing to, no matter what type the pointer may really be pointing to. This is a consequence of C++'s "static type checking"; if we were allowed to refer to a member variable or function that might theoretically not be present in an object at run time, the compiler would have no way of knowing whether what we were trying to do was legal. Therefore, if we want to access a member variable called m_Count through a StockItem*, there has to be a member variable called m_Count in the StockItem class, even if we don't need it until we get to a class derived from StockItem.
This also brings up another point that may or may not be obvious to you: the workings of the polymorphic StockItem object don't depend on the fact that the DatedStockItem class is derived from UndatedStockItem. So long as both the UndatedStockItem and DatedStockItem classes are derived directly or indirectly from StockItem, we can use a StockItem* to refer to an object of either of these classes, UndatedStockItem or DatedStockItem, which is all that we need to make the idiom work.

Executing Code before the Beginning of main

We're almost ready to review what we've covered in this chapter, but first there's one issue that I have deferred explaining until now: how we can arrange for code to be executed before the beginning of main, and why we would want to do that.

Let's consider what would happen if we were to define a variable of a user-defined type as a global object. We know that all global objects are initialized before the beginning of main, according to the C++ language specification. But we also know that all objects of user-defined types are created via constructors, either written by us or created automatically by the compiler. When we define such a variable as a global object, the appropriate constructor is called before the beginning of main. Therefore, if we want to make sure that a particular piece of code is executed before the beginning of main, all we have to do is put it in a constructor and define a global object that is created by that constructor.

Why would we want to do that? To make polymorphism more flexible without losing the advantages of our "safe polymorphism" approach.

With our current implementation of safe polymorphism, our base StockItem class has to know about all the derived types that we create, because the constructors that create those derived types are called from constructors in the StockItem class.

But what if we wanted to be able to add new user-defined types derived from StockItem without changing the implementation of StockItem in any way? We can do this by defining special constructors for each derived type, then creating global objects of those types that use those special constructors. The special constructors are called before main and register their classes with a central "polymorphic object class registry function" that maintains a list of available classes. Whenever another part of the program wants to create an object of a polymorphic object type, it can ask the registry to create that object, providing whatever parameters are needed to select the appropriate type and initialize the object. The registry does this by calling an object creation function in the selected class. This approach enables us to add or remove classes when we link the program without making changes to the rest of the program, which allows other programmers to extend our classes without needing our source code.

10.14. Review

We started this chapter with the DatedStockItem class from Chapter 9, which extended the StockItem class by adding an expiration date field to the member variables that DatedStockItem inherited from the StockItem class. While this was a solution to the problem of creating a class based on the StockItem class without having to rewrite all the functioning code in the latter class, it didn't solve the bigger problem: how to create a Vec of objects that might or might not have expiration dates. That is, we wanted to be able to mix StockItem objects with DatedStockItem objects in the same Vec, which can't be done directly in C++.

Part of this difficulty was solved easily enough by making a Vec of StockItem*s (i.e., pointers to StockItems), rather than StockItems, to take advantage of the fact that C++ allows us to assign the address of an object of a derived type to a pointer to its base class (e.g., to assign the address of a DatedStockItem to a pointer of type StockItem*). Creating such a Vec of StockItem*s allowed us to create both StockItems and DatedStockItems and to assign the addresses of these objects to various elements of that Vec. Even so, this didn't solve the problem completely. When we called the Reorder function through a StockItem pointer, the function that was executed was always StockItem::Reorder. This result may seem reasonable, but it doesn't meet our needs. When we call the Reorder function for an object, we want to execute the correct Reorder function for the actual type of the object the pointer is referring to even though the pointer is declared as a StockItem*.

The reason the StockItem function is always called in this situation is that when we make a base class pointer (e.g., a StockItem*) refer to a derived class object (e.g., a DatedStockItem), the compiler doesn't know what the actual type of the object is at compile time. By default, the compiler determines exactly which function will be called at compile time, and the only information the compiler has about the type of an object pointed to by a StockItem* is that it's either a StockItem or an object of a class derived from StockItem. In this situation, the compiler defaults to the base class function.

The solution to this problem is to make the Reorder function virtual. This means that when the compiler sees a call to the Reorder function, it generates code that will call the appropriate version of that function for the actual type of the object referred to. In this case, StockItem::Reorder is called if the actual object being referred to through a StockItem* is a StockItem, while DatedStockItem::Reorder is called if the actual object being referred to through a StockItem* is a DatedStockItem. This is exactly the behavior we need to make our StockItem and DatedStockItem objects do the right thing when we refer to them through a StockItem*.

To make this run-time determination of which function will be called, the compiler has to add something to every object that contains at least one virtual function. What it adds is a pointer to a vtable (virtual function table), which contains the addresses of all the virtual functions defined in the class of that object or declared in any of its ancestral classes. The code the compiler generates for a virtual function call uses this table to look up the actual address of the function to be called at run time.

After going over the use of virtual functions, we looked at the implementation of the usual I/O functions, operator << and operator >>. Even though these can't be virtual functions, as they aren't member functions at all, they can still use the virtual mechanism indirectly by calling virtual functions that provide the correct behavior for each class. However, while this solves most of the problems with operator <<, the implementation of operator >> is still pretty tricky. It has to allocate the memory for the object it is creating because the actual type of that object isn't known until the data for the object has been read. The real problem here, though, is not that operator >> has to allocate that memory, or even that it has to use a reference to a pointer to notify the calling function of the address of the allocated memory. The big difficulty is that after operator >> allocates the memory, the calling function has to free the memory when it is done with the object. Getting a program written this way to work properly is very difficult; keeping it working properly after later changes is virtually impossible. Unfortunately, there's no solution to this problem as long as we require the user program to use a Vec of StockItem*s to get the benefits of polymorphism.

As is often the case in C++, though, there is a way to remove that requirement. We can hide the pointers from the user, and eliminate the consequent memory allocation problems, by creating a polymorphic object using the manager/worker idiom. With these idioms, the user sees only a base class object, within which the pointers are hidden. The base class is the manager class, which uses an object of one of the worker classes to do the actual work.

Possibly the most unusual aspect of this implementation of the manager/worker idiom is that the type of the pointer inside the manager object is the same as the type of the manager class. In the current case, each StockItem object contains a StockItem* called m_Worker as its main data member. This may seem peculiar, but it makes sense. The actual type of the object being referred to is always one of the worker classes, which in this case means either UndatedStockItem or DatedStockItem. Since we know that a StockItem* can refer to an object of any of the derived classes of StockItem, and because we want the interface of the StockItem class to be the same as that of any of its derived classes, declaring the pointer to the worker object as a StockItem* is quite appropriate.

We started our examination of this new StockItem class with operator <<, whose header indicates one of the advantages of this new implementation of StockItem. Rather than taking a StockItem*, as the previous version of operator << did, it takes a const reference to a StockItem. The implementation of this function consists of a call to a virtual function called Write. Since Write is virtual, the exact function that will be called here depends on the actual type of the object pointed to by m_Worker, which is exactly the behavior we want.

After going into more detail on how this "internal polymorphism" works, we looked at how such a polymorphic object comes into existence in the first place, starting with the default constructor for StockItem. This constructor is a bit more complicated than it seems at first. When it creates an empty UndatedStockItem to perform the duties of a default-constructed StockItem, that newly constructed UndatedStockItem has to initialize its base class part, as is required for all derived class objects. That may not seem too unusual, but keep in mind that the base class for UndatedStockItem is StockItem; therefore, the constructor for UndatedStockItem will necessarily call a constructor for StockItem. We have to make sure that it doesn't call the default constructor for StockItem, as that is where the UndatedStockItem constructor was called from in the first place! The result of such a call would be a further call to the default constructor for UndatedStockItem, then to the default constructor for StockItem, and so on forever (or at least until we had run out of stack space). The solution is simple enough: we have to create a special constructor for StockItem, StockItem::StockItem(int), that we call explicitly via a base class initializer in the default and normal constructors for UndatedStockItem. This special constructor doesn't do anything but initialize m_Worker to 0, for reasons we'll get to later, and then return to its caller. Since it doesn't call any other functions, we avoid the potential disaster of an infinite regress.

This simple function, StockItem::StockItem(int), does have a couple of other interesting features. First, I declared it protected so that it couldn't be called by anyone other than our member functions and those of our derived classes. This is appropriate because, after all, we don't want a user to be able to create a StockItem with just an int argument. In fact, we are using that argument only to allow the compiler to tell which constructor we mean via the function overloading facility. This also means that we don't need to provide a name for that argument, since we're not using it. Leaving the name out tells the compiler that we didn't accidentally forget to use the argument, so we won't get "unused argument" warnings when we compile this code. Any programmers who may have to work on our code in the future will also appreciate this indication that we weren't planning to use the argument.

Then we examined the use of reference-counting to keep track of the number of users of a given DatedStockItem or UndatedStockItem object, rather than copying those objects whenever we copied their manager objects. As long as we keep track of how many users there are, we can safely delete the DatedStockItem or UndatedStockItem as soon as there are no more users left. To keep track of the number of users, we use the m_Count member variable in the StockItem class.

To see how this works in practice, we went through the actions that occur when StockItem objects are created, copied, and destroyed. This involves the implementation of the assignment operator, which copies the pointer to the worker object contained in the manager object. We can do this because we are using reference-counting to keep track of the number of users of each object, so memory is freed correctly when the objects are destroyed at the end of the function where they are created.

The destructor for the StockItem class has a number of new features. First, it is virtual, which is necessary because the derived class object pointed to by the StockItem object must be destroyed when it no longer has any users, but the type of the pointer through which it is accessed is StockItem*. To allow the compiler to call the correct destructor - the one for the derived class object - we must declare the base class destructor virtual. This is the same rule that applies to all other functions that have to be resolved according to the run-time type of the object for which they are called.

Second, the StockItem destructor has to check whether it has been called to destroy the base class part of a derived class object. Just as in the case of the constructor for StockItem, we have to prevent an infinite regress in which the destructor for StockItem calls the destructor for DatedStockItem, which calls the destructor for StockItem, which calls the destructor for DatedStockItem again, and so on. In fact, in this case we can't avoid the first "round trip" because the destructor for DatedStockItem must call the destructor for StockItem; we don't have any control over that. However, the first if statement in the StockItem destructor cuts off the regress right there, as m_Worker will be 0 in the base class part of a derived class object. That's why we initialized m_Worker to 0 in the special constructor for StockItem that was used to construct the base class part of an object of a class derived from StockItem.

We finished by examining the exact sequence of events that occurs when the objects in the example program are destroyed. We also determined the reason that we had to include m_Count in the base class, when it was never used there: the type of the pointer we use to access m_Count is a StockItem*, so there has to be an m_Count variable in a StockItem. If there were no such variable in the StockItem class, the compiler couldn't guarantee that it would exist in the object pointed to by a StockItem pointer and therefore wouldn't let us access it through such a pointer.

10.15. Exercises, Second Set

3. Rewrite the DrugStockItem class that you wrote earlier in this chapter as a derived class of DatedStockItem, adding the new class to the polymorphic object implementation based on the StockItem class. The Reorder member function of the DrugStockItem class will be inherited from DatedStockItem, and the member function DeductSaleFromInventory will have to be made a virtual function in StockItem so that the correct version will be called via the StockItem* in the StockItem class. The resulting set of classes will allow the effective type of a StockItem object to be any of UndatedStockItem, DatedStockItem, or DrugStockItem.
4. Rewrite the Employee, Exempt, and Hourly classes that you wrote earlier in this chapter as a set of classes implementing a polymorphic object type. The base class will be Employee, with an Exempt class and an Hourly class derived from the base class. The resulting set of classes will allow the effective type of an Employee object to be either Exempt or Hourly, with the CalculatePay function producing the correct result for either type. To distinguish the effective types, you will need to write two different versions of the constructor for the Employee class. Do this by adding an additional argument of type double in the constructor that creates an Hourly worker object, specifying the multiplier used to calculate overtime pay. For example, a value of 1.5 specifies the standard "time-and-a-half for overtime" multiplier. You can declare a variable of type double just as you can a variable of any other type, e.g. double x; declares a variable named x of type double. Note that you must maintain the same interface for the CalculatePay function in these classes because you are using a base class pointer to access derived class objects.
5. What would happen if we tried to assign a StockItem object to itself using the operator = implementation in Figure 10.40 on page 731?

10.16. Conclusion

Now that we have enough tools to work with, it's time to tackle a more realistic project. That's what we'll do in the next chapter, where we start to develop a home inventory system.
1 We could also use a reference, as we'll see in the implementation of the << and >> operators. However, that still wouldn't provide the flexibility of using real objects. In particular, you can't create a Vec of references.

2 You will notice that the virtual declaration for Reorder is repeated in DatedStockItem - this is optional. Even if you don't write virtual again in the derived class declaration of Reorder, it's still a virtual function in that class; the rule is "once virtual, always virtual". Even so, I think it's clearer to explicitly state that the derived class function is virtual, so that's how I will show it in this book.

3 Please note that the layout of this figure and other similar figures has been simplified by the omission of the details of the m_Name field, which actually contains a pointer to the data of the string value of that field.

4 As with other virtual functions, if a base class destructor is virtual, the destructors in all classes derived from that class will also automatically be virtual, so we don't have to make them virtual explicitly.

5 Manager/worker is my name for what James Coplien calls the envelope/letter idiom in his book, Advanced C++: Programming Styles and Idioms (ISBN 0-201-54855-0, Addison-Wesley Publishing Company, Reading, Mass., 1992).

6 If you were wondering why the file name is different in this program than it was in the program in Figure 10.18, it's because the program in Figure 10.21 is using the "real" version of the StockItem class rather than the simplified one used by the program in Figure 10.18. Therefore, it needs more input to fill in the extra member variables.

7 This is the same problem we saw with the compiler-generated copy constructor and the compiler-generated assignment operator for the string class we created earlier in this book. See the discussion beginning at the heading "Why We Need a Reference Argument for operator =" on page 474.

8 There is actually another requirement for correct sharing of data in this situation. We mustn't make changes to the data in a shared worker object that has more than one user. In the current application, we don't have that problem because we aren't changing the data after the objects have been created. However, this is something we'll have to deal with in Chapter 11, when we develop a polymorphic object type whose objects can be changed after creation.

9 In this and the following diagrams, I've omitted a number of the data elements to make the figure fit on the page.


TOC PREV NEXT INDEX