CHAPTER 9 Inheritance
We're going to continue our investigation of C++ concepts and practices by revisiting the inventory control example from Chapter 6. We'll build on the StockItem class that we created there by using one of the primary organizing principles of C++ that we haven't encountered before: inheritance. First, let's define this term and a few others that we'll be using in this chapter; then we'll take a look at the objectives.
9.1. Definitions
Inheritance is the definition of one class as a more specific version of another class that has been previously defined. The newly defined class is called the derived (or sometimes child) class, and the previously defined class is called the base (or sometimes parent) class. In this book, we will use the terms base and derived. The derived class inherits all of the member variables and regular member functions from the base class. Inheritance is one of the primary organizing principles of object-oriented programming.
A regular member function is a member function that is not in any of the following categories:
1. constructor,
2. destructor,
3. assignment operator (i.e., operator =).
A member function in a derived class is said to override a base class member function if the derived class function has the same signature (name and argument types) as that of the base class member function. The derived class member function will be called instead of the base class member function when the member function is referred to via an object of the derived class. A member function in a derived class with the same name but a different signature from that of a member function in the base class does not override the base class member function. Instead, it "hides" that base class member function, which is no longer accessible as a member function in the derived class.
For example, the function Reorder(ostream &) may be defined in a base class (StockItem) and in a derived class (DatedStockItem). When Reorder is called via an object of the base class StockItem, the base class version of Reorder will be called; when Reorder is called via an object of the derived class DatedStockItem, the derived class version of Reorder will be called. This behavior of C++ allows a derived class to supply the same functionality as a base class but implement that functionality in a different way.
A manipulator is a special type of member function of one of the iostream classes. Such a function controls how output will be formatted without itself necessarily producing any output.
A static member function is a member function of a class that can be called without reference to an object of that class. Such a function has no this pointer passed to it on entry, and therefore it cannot refer to member variables of the class.
Buffering is the use of a buffer to store or retrieve information.
A normal constructor is a constructor whose arguments supply enough information to initialize all of the member fields in the object being created.
The base class part of a derived class object is an unnamed component of the derived class object. Member variables and functions of a base class part declared in the public or protected part of the base class interface are accessible in the derived class member functions as though they were defined in the derived class.
The keyword protected is an access specifier. When present in a base class definition, it allows derived class functions access to members in the base class part of a derived class object while preventing access by other functions outside the base class.
9.2. Objectives of This Chapter
By the end of this chapter, you should
1. understand how we can use inheritance to create a new class by extending an existing class, and
2. understand how to use manipulators to control the format of iostream output.
Two Reasons to Use Inheritance
Before we return to the detailed examination of our inventory control classes (StockItem and its companion class Inventory) let's expand a bit on the first objective as it applies to this case.
There are two reasons to use inheritance. The first is to create a new class that has all of the capabilities of an existing class while adding capabilities that are unique to the new class. In such a case, objects of the new class are clearly not equivalent to objects of the existing class, which means that the user of these classes has to know which class any given object belongs to so that he or she can tell which operations that object can perform. We could call this use of inheritance "inheritance for extension". It's illustrated by one of the Employee class exercises in this chapter.1
In the current case, however, we'll be using inheritance to create a new class called DatedStockItem that will behave exactly like the StockItem class except that its items will have expiration dates. As a result, the user of these classes will be able to treat objects of the new DatedStockItem class in the same way as those of the base class. Of course, to create an object of this new class, the expiration date for the object must be provided, but once such an object exists its user can view it exactly as if it were as an object of the base class, which makes this an example of "inheritance for reimplementation". In such a case, it is reasonable to be able to substitute objects of the derived class for those of the base class. This relationship between a base class and a derived class is commonly expressed by saying that the derived class "isA" base class object. We will see how to use that characteristic of inheritance in the next chapter.
Before we can do that, though, we'll need to learn how to create a new class by derivation from an existing class. In this case, we're going to start with a version of our previous StockItem class that has been extended slightly to include a Reorder function that generates a reordering report when we get low on an item, as well as some new input and output facilities. We'll also need to improve on our companion Inventory class, which as before we'll use to keep track of all the StockItems in the store.
Now let's get to the details of how this version of the StockItem class works. Figure 9.1 shows the header file for that class.
Here's a rundown on the various member functions of the StockItem class, including those that we've already seen:
1. StockItem(); is the default constructor.
2. StockItem(string Name, short InStock, short Price, short MinimumStock, short MinimumReorder, string Distributor, string UPC); is the normal constructor, which has a couple of new arguments, MinimumStock and MinimumReorder, which are needed for the reordering function.
3. void FormattedDisplay(ostream& os); displays the member variables of a StockItem object with labels so you can tell which value is for which member variable.
4. bool CheckUPC(string ItemUPC); returns true if its argument is the same as the UPC (i.e., the m_UPC member variable) of its StockItem.
5. void DeductSaleFromInventory(short QuantitySold); reduces the inventory (i.e., the value of the m_InStock member variable) by the value of its argument.
6. short GetInventory(); returns the number of items in stock for this StockItem (i.e., the value of the m_InStock member variable).
7. string GetName(); returns the name of the StockItem (i.e., the value of the m_Name member variable).
8. string GetUPC(); returns the UPC of the StockItem (i.e., the value of the m_UPC member variable).
9. bool IsNull(); returns true if this is a "null StockItem". This can happen, for example, when a StockItem is returned as a "not found" value by a search.
10. short GetPrice(); returns the price of the StockItem (i.e., the value of the m_Price member variable).
11. void Reorder(ostream& os); generates a reorder report based on the relationship of the number in stock (m_InStock) versus the minimum desired stock (m_MinimumStock), taking the minimum reorder quantity (m_MinimumReorder) into account.
FIGURE 9.1. The next StockItem header file (code\item20.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(std::string Name, short InStock,
short Price, short MinimumStock,
short MinimumReorder, std::string Distributor, std::string UPC);
void FormattedDisplay(std::ostream& os);
bool CheckUPC(std::string ItemUPC);
void DeductSaleFromInventory(short QuantitySold);
short GetInventory();
std::string GetName();
std::string GetUPC();
bool IsNull();
short GetPrice();
void Reorder(std::ostream& os);
private:
short m_InStock;
short m_Price;
short m_MinimumStock;
short m_MinimumReorder;
std::string m_Name;
std::string m_Distributor;
std::string m_UPC;
};
Here's a brief description of the input/output operators for this class, which will allow us to read and write its objects in much the same way as we can do with the built-in types and the string class we've created earlier in the book:
1. friend ostream& operator << (ostream& os, const StockItem& Item); sends a human-readable version of the state of a StockItem object to the ostream specified as the left-hand argument to <<. This is analogous to the use of operator << for output of the built-in types.
2. friend istream& operator >> (istream& is, StockItem& Item); creates a StockItem object by reading a human-readable version of the state of the object from the istream specified as the left-hand argument to >>. This is analogous to the use of operator >> for input of the built-in types.
Susan wanted to know why we needed the FormattedDisplay function.
Susan: Do we need the FormattedDisplay to make the data appear on the screen the way we want it? I mean, does the FormattedDisplay function do something that we can't do by just using operator <<?
Steve: Yes. It puts labels on the data members so you can tell what they are.
Figure 9.2 shows the implementation of the StockItem class that we will start our inheritance exercise from.
FIGURE 9.2. The next implementation of StockItem (code\item20.cpp)
#include <iostream>
#include <string>
#include "item20.h"
using namespace std;
StockItem::StockItem()
: m_InStock(0), m_Price(0), m_MinimumStock(0),
m_MinimumReorder(0), m_Name(), m_Distributor(),
m_UPC()
{
}
StockItem::StockItem(string Name, short InStock,
short Price, short MinimumStock,
short MinimumReorder, string Distributor, string UPC)
: m_InStock(InStock), m_Price(Price),
m_MinimumStock(MinimumStock),
m_MinimumReorder(MinimumReorder), m_Name(Name),
m_Distributor(Distributor), m_UPC(UPC)
{
}
void StockItem::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 << "Minimum Reorder quantity: ";
os << m_MinimumReorder << endl;
os << "Distributor: ";
os << m_Distributor << endl;
os << "UPC: ";
os << m_UPC << endl;
}
ostream& operator << (ostream& os, const StockItem& Item)
{
os << Item.m_Name << endl;
os << Item.m_InStock << endl;
os << Item.m_Price << endl;
os << Item.m_MinimumStock << endl;
os << Item.m_MinimumReorder << endl;
os << Item.m_Distributor << endl;
os << Item.m_UPC << endl;
return os;
}
istream& operator >> (istream& is, StockItem& Item)
{
getline(is,Item.m_Name);
is >> Item.m_InStock;
is >> Item.m_Price;
is >> Item.m_MinimumStock;
is >> Item.m_MinimumReorder;
is.ignore();
getline(is,Item.m_Distributor);
getline(is,Item.m_UPC);
return is;
}
bool StockItem::CheckUPC(string ItemUPC)
{
if (m_UPC == ItemUPC)
return true;
return false;
}
void StockItem::DeductSaleFromInventory(short QuantitySold)
{
m_InStock -= QuantitySold;
}
short StockItem::GetInventory()
{
return m_InStock;
}
string StockItem::GetName()
{
return m_Name;
}
string StockItem::GetUPC()
{
return m_UPC;
}
bool StockItem::IsNull()
{
if (m_UPC == "")
return true;
return false;
}
short StockItem::GetPrice()
{
return m_Price;
}
void StockItem::Reorder(ostream& os)
{
short ActualReorderQuantity;
if (m_InStock < m_MinimumStock)
{
ActualReorderQuantity = m_MinimumStock - m_InStock;
if (m_MinimumReorder > ActualReorderQuantity)
ActualReorderQuantity = m_MinimumReorder;
os << "Reorder " << ActualReorderQuantity;
os << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " from " << m_Distributor << endl;
}
}
Susan had a lot of questions about the operator << and operator >> functions for this class as well as about streams in general.
Susan: Why do you have to define these functions again?
Steve: They have to be defined for every class of objects we want to be able to use them for. After all, every class of objects has different data items in it; how is a stream supposed to know how to read or write some object that we've made up, unless we tell it how to?
Susan: What's "is" again? I forgot. :-P
Steve: The istream that we're using to get the data for the StockItem.
Susan: So, it's just a file? Is it always called is?
Steve: No, it's not a file; it's an istream, which is an object connected to a file that allows us to read from the file using >>.
Susan: Do you mean any file that has >> or <<? If it is like an istream where does the data end up? Just how does it work? When does the istream start flowing and at what point does the data jump in and get out? What is the istream doing when there is no data to be transported? Where is it flowing? If it is not a file, then where is it stored? So, whenever you read something from an istream, is it always called "is"?
Steve: Obviously streams are going to take a lot more explaining, with pictures. We'll get to it later in this chapter.2
9.3. Taking Inventory
The StockItem class is designed to keep track of an individual item in the inventory, but we also need a way to keep track of all the StockItems in the store. So let's take a look at the next version of the class that serves that purpose in the inventory control application, Inventory. Figure 9.3 shows the header file for this version of the Inventory class, which is essentially identical to the last one from Chapter 6, except that we have added a ReorderItems function to allow us to generate a reorder report for all of the items in stock.
FIGURE 9.3. The next header file for the Inventory class (code\invent20.h)
class Inventory
{
public:
Inventory();
short LoadInventory(std::istream& is);
void StoreInventory(std::ostream& os);
StockItem FindItem(std::string UPC);
bool UpdateItem(StockItem Item);
void ReorderItems(std::ostream& os);
private:
Vec<StockItem> m_Stock;
short m_StockCount;
};
Besides the default constructor, this class has several other member functions that we should discuss briefly, including those that we have already discussed in Chapter 6.
1. LoadInventory reads data from an istream to create StockItem objects for the inventory.
2. StoreInventory writes the current StockItem data to an ostream to save it for posterity.
3. FindItem locates an item in the inventory by its UPC.
4. UpdateItem updates the data for an item in the inventory.
5. ReorderItems calls each item in the inventory and asks it to generate a line for the reordering report, which tells the user how many of each item need to be reordered from the distributor.
Susan had some questions about the arguments to LoadInventory and StoreInventory:
Susan: What are is and os? Why didn't you talk about them?
Steve: They're the names of the reference arguments of type istream and ostream, respectively, as indicated in the header file. They allow us to access the streams that we use to read data from the input file and write data to the output file.
9.4. Adding ReorderItems to the Inventory class
Now let's examine the details of the part of this inventory control program that calculates how much of each item has to be ordered to refill the stock. As I mentioned previously, I've chosen the imaginative name ReorderItems for the member function in the Inventory class that will perform this operation. The ReorderItems function is pretty simple. Its behavior can be described as follows:
`For each element in the StockItem Vec in the Inventory object, call its member function Reorder to generate an order if that StockItem object needs to be reordered.'
Of course, this algorithm is much simpler than we would need in the real world; however, it's realistic enough to be useful in illustrating important issues in program design and implementation.
Figure 9.4 shows the version of the inventory test program that uses the initial versions of those classes that we will build on in our inheritance exercise.
FIGURE 9.4. The StockItem test program for the base StockItem class (code\itmtst20.cpp)
#include <iostream>
#include <fstream>
#include "Vec.h"
#include "item20.h"
#include "invent20.h"
using namespace std;
int main()
{
ifstream ShopInfo("shop20.in");
ofstream ReorderInfo("shop20.reo");
Inventory MyInventory;
MyInventory.LoadInventory(ShopInfo);
MyInventory.ReorderItems(ReorderInfo);
return 0;
}
This shouldn't be too hard to follow. We start by creating an ifstream (i.e., an input stream that can be connected to a file) and an ofstream (i.e., an output stream that can be connected to a file). Then we create an inventory object called MyInventory, load items into it from the ifstream, and call the ReorderItems function to create the reorder report that is written to the ofstream. Finally, we return 0 to indicate successful completion.
Assuming that you've installed the software from the CD in the back of this book, you can try out this program. First, you have to compile it by following the compilation instructions on the CD. Then type itmtst20 to run the program. However, this program doesn't have much of a user interface so you might want to watch it operating under the debugger, by following the usual instructions for that method. When the program terminates, you can look at its output file, shop20.reo, to see what the reorder report looks like.
Now let's take a look at how the Inventory class does its work (Figure 9.5).3
FIGURE 9.5. The implementation of the Inventory class (code\invent20.cpp)
: m_Stock (Vec<StockItem>(100)),
short Inventory::LoadInventory(istream& is)
StockItem Inventory::FindItem(string UPC)
for (i = 0; i < m_StockCount; i ++)
if (m_Stock[i].GetUPC() == UPC)
bool Inventory::UpdateItem(StockItem Item)
for (i = 0; i < m_StockCount; i ++)
if (m_Stock[i].GetUPC() == UPC)
void Inventory::StoreInventory(ostream& os)
for (i = 0; i < m_StockCount; i ++)
void Inventory::ReorderItems(ostream& os)
for (i = 0; i < m_StockCount; i ++)
The ReorderItems function can hardly be much simpler. As you can see, it merely tells each StockItem element in the m_Stock Vec to execute its Reorder function. Now let's see what the latter function, whose full name is void StockItem::Reorder(ostream&), needs to do:
1. Check to see if the current stock of that item is less than the desired minimum.
2. If we are below the desired stock minimum, order the amount needed to bring us back to the stock minimum, unless that order amount is less than the minimum allowable quantity from the distributor. In the latter case, order the minimum allowable reorder quantity.
3. If we are not below the desired stock minimum, do nothing.
To support this Reorder function, StockItem uses the data items m_MinimumStock and m_MinimumReorder to calculate the number of units of the current StockItem object that have to be reordered. Figure 9.6 shows the code for the Reorder function.
FIGURE 9.6. The Reorder function for the StockItem class (from code\item20.cpp)
void StockItem::Reorder(ostream& os)
{
short ActualReorderQuantity;
if (m_InStock < m_MinimumStock)
{
ActualReorderQuantity = m_MinimumStock - m_InStock;
if (m_MinimumReorder > ActualReorderQuantity)
ActualReorderQuantity = m_MinimumReorder;
os << "Reorder " << ActualReorderQuantity;
os << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " from " << m_Distributor << endl;
}
}
Here's the translation of this code:
1. If the number of units in stock is less than the minimum number desired, we calculate the number needed to bring the inventory back to the minimum.
2. However, the number we want to order may be less than the minimum we are allowed to order; the latter quantity is specified by the variable m_MinimumReorder.
3. If the amount we need is less than m_MinimumReorder, we have to order the latter quantity
4. Finally, we display the order for the item. Of course, if we already have enough units in stock we don't have to reorder anything, so we don't display anything.
Susan had a question about the implementation of this function:
Susan: So, are you ordering more than needed in some cases?
Steve: Yes, if that's the minimum number that can be ordered.
9.5. Adding Expiration Dates
Now we want to add one wrinkle to this algorithm: handling items that have expiration dates. This actually applies to a fair number of items in a typical grocery store, including dairy products, meats, and even dry cereals. To keep things as simple as possible, we'll assume that whenever we buy a batch of some item with an expiration date, all of the items of that type have the same date. When we get to the expiration date of a given StockItem, we send back all of the items and reorder as though we had no items in stock.
The first question is how we store the expiration date. My first inclination was to use an unsigned short to store each date as a number representing the number of days from (for example) January 1, 2000, to the date in question. Since there are approximately 365.25 days in a year, the range of 65535 days should hold us roughly until the year 2179, which should be good enough for our purposes. Perhaps by that year, we'll all be eating food pills that don't spoil.4
However, storing a date as a number of days since a "base date", such as January 1, 2000, does require a means of translating a human-readable date format like "September 4, 2002" into a number of days from the base date and vice versa. Owing to the peculiarities of our Gregorian calendar (primarily the different numbers of days in different months and the complication of leap years), this is not a trivial matter and is a distraction from our goal here.
However, if we represent a date as a string of the form YYYYMMDD, where YYYY is the year, MM is the month, and DD is the day within the month, we can use the string comparison functions to tell us which of two dates is later than the other one.5 Here's the analysis:
1. Of two dates with different year numbers, whichever has the higher year number is a later date.
2. Of two dates with the same year number but different month numbers, whichever has the higher month number is a later date.
3. Of two dates having the same year and month numbers, whichever has the higher day number is a later date.
Because the string comparison operators compare bytes from left to right and stop when a mismatch is detected, as is needed for alphabetical sorting, it should be clear that dates using the representation YYYYMMDD will have their year numbers compared first, followed by the month numbers if needed, followed by the day numbers if needed. Thus, comparing two strings via string::operator > will produce the result true if the "date string" on the left represents a date later than the "date string" on the right, exactly as it should.
Now that we've figured out that we can store the expiration date as a string, how do we arrange for it to be included in the StockItem object? One obvious solution is to make up a new class called, say, DatedStockItem, by copying the interface and implementation from StockItem, adding a new member variable m_Expires, and modifying the copied Reorder member function to take the expiration date into account. However, doing this would create a maintenance problem when we had to make a change that would affect both of these classes, as we'd have to make such a change in two places. Just multiply this nuisance ten or twenty times and you'll get a pretty good idea of how program maintenance has acquired its reputation as difficult and tedious work.6
Susan had some questions about this notion of program maintenance:
Susan: What kind of change would you want to make? What is maintenance? What is a typical thing you would want to do to some code?
Steve: Maintenance means any kind of change to a program after it is already being used. For example, if we decide that a short isn't the right type of variable to hold the price, then we have to change its definition to some other type. We'll see lots of examples of maintenance in Chapter 12.
Since one of the purposes of object-oriented programming is to reduce the difficulty of maintaining programs, surely there must be a better way to create a new class "just like" StockItem but with an added member variable and a modified member function to use it.
Reducing Maintenance Via Inheritance
Yes, there is; it's called inheritance. We can define our new class called DatedStockItem with a notation that it inherits (or derives) from StockItem. This makes StockItem the base class (sometimes referred to as the parent class) and our new DatedStockItem class the derived class (sometimes referred to as the child class). By doing this, we are specifying that a DatedStockItem includes every data member and regular member function a StockItem has. Since DatedStockItem is a separate class from StockItem, when we define DatedStockItem we can also add whatever other functions and data we need to handle the differences between StockItem and DatedStockItem.
Susan wanted to clarify some terms:
Susan: Are inheritance and derivation the same thing?
Steve: Yes. To say that B inherits from A is the same as saying that B is derived from A.
She also had some questions about the relationship between the notions of friend and inheritance.
Susan: How about a little reminder about friend here, and how about explaining the difference between friend and inheritance, other than inheritance being an entirely different class. They kinda do the same thing.
Steve: When, in a class definition, you make a function or class a friend of the one you're defining, the friend function or class has access to all members of the class you are defining, disregarding their access specifiers; however, the friend has no other relationship to the class being defined. That is, making class B a friend to class A does not make a B object a substitute for an A object.
On the other hand, if B is (publicly) derived from A, then a B object can be used wherever an A object can be used.
I think a picture might help here. Let's start with a simplified version of the StockItem and DatedStockItem classes, whose interface is shown in Figure 9.7. I recommend that you print out the file that contains these interfaces (code\itema.h) for reference as you go through this section of the chapter.
FIGURE 9.7. Simplified interface for StockItem and DatedStockItem classes (code\itema.h)
class StockItem
{
public:
StockItem(std::string Name, short InStock, short MinimumStock);
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);
void Reorder(std::ostream& os);
protected:
static std::string Today();
protected:
std::string m_Expires;
};
Given these definitions, a StockItem object might look as depicted in Figure 9.8.7
FIGURE 9.8.A simplified StockItem object
And a DatedStockItem object might look as depicted in Figure 9.9.
As you can see, an object of the new DatedStockItem class contains a StockItem as part of its data. In this case, that base class part accounts for most of the data of a DatedStockItem; all we've added is a data member called m_Expires. In fact, a derived class object always contains all of the variables and "regular" member functions in the base class because the derived class object effectively has an object of the base class embedded in it, as indicated in Figure 9.9. We can access those member variables and functions that are part of the base class part of our derived class object exactly as though they were defined in the derived class, so long as their access specifiers are either public or protected. Although the public and private access specifiers have been part of our arsenal of tools for some time, this is our first encounter with the protected access specifier. We'll see shortly that the sole purpose of the protected access specifier is to allow derived class member functions to use member functions and variables of the base class part of an object of that derived class, while protecting those member functions and variables from use by unrelated classes.
FIGURE 9.9.A DatedStockItem object
Susan had some interesting comments and questions about the notion of the base class part of a derived class object.
Susan: When I look at Figure 9.9 I get the feeling that every DatedStockitem object contains a StockItem object; is this the "base class part of the derived class object"?
Steve: Yes.
Susan: The word "derived" is confusing; if a DatedStockItem is derived from a StockItem one tends to take a linear approach as in a family tree, which isn't quite right. It would be better to think of DatedStockItem as a fruit like a plum, with the pit being the StockItem, which is the core of the object.
Steve: Or maybe an onion, where all the layers are edible, rather than making the distinction between the flesh of the fruit and an inedible pit.
Susan: But if so, every member function of the derived class could access every member variable of the base class because "They occur as the base class part of a derived class object".
Steve: No, as we'll see, even though private members are actually present in the derived class object, they are not accessible to derived class functions. That's why we need protected.
Of course, as noted before, we don't have to rely solely on the facilities we inherit from our base class; we can also add whatever new functions or variables we need to provide the new functionality of the new class. As you will see, we don't want or need to add any public member functions in the present case because our eventual goal is to allow the application programmer to treat objects of the new DatedStockItem class as equivalent to objects of the StockItem class. To reach this goal, these two classes must have the same class interface, i.e., the same public member functions.
A Note on Proper Object-oriented Design
By the way, for proper object-oriented design, it's not enough just to have the same names for the member functions in the derived class; they have to have the same meanings as well. That is, the user shouldn't be surprised by the behavior of a derived class function if he knows how the base class function behaves. For example, if the DatedStockItem Reorder function were to rearrange the items in the inventory rather than generate a reorder report, as the StockItem version does, the user would get very confused! The solution to this problem is simple: make sure that your derived class functions do the "same thing" as the corresponding base class functions, differing only in how they do it.
Overriding a Base class Function
Now let's get back to the implementation of the DatedStockItem class. Instead of adding new public member functions, we will override the base class version of Reorder by writing a new version of Reorder for our DatedStockItem class. Our new function, which has the same signature as that of the base class Reorder function, will use the new data member m_Expires. Since StockItem::Reorder has been overridden by DatedStockItem::Reorder, the latter function will be called whenever the user's program calls the Reorder function of a DatedStockItem.
Susan wasn't sure about the meaning of "overriding" a base class function rather than writing an entirely new one. That discussion led to a more general one about the whole idea of inheritance.
Susan: Why is the term "override" used here? The derived class member function is called for an object of the derived class, so I don't see how it "overrides" the base class member function with the same signature.
Steve: What would happen if we didn't write the derived class function? The base class function would be called. Therefore, the derived class function is overriding the previously existing base class function.
Susan: How does this differ from hiding? Give me an example.
Steve: If we wrote a Reorder function with a different signature in the derived class, such as DatedStockItem::Reorder(int), that would no longer override the base class function, StockItem::Reorder(ostream&), but would hide the base class function. This would mean that the base class function would not be called even if the argument was an ostream. Instead, calling Reorder for a DatedStockItem object with an ostream argument in that case would be an error.
Susan: Ok, I guess I see the difference. But why do you write a new version of Reorder instead of adding a new public member function?
Steve: Precisely because our eventual goal is to allow the user to use stock items with and without dates interchangeably. If StockItem and DatedStockItem had different names for their reordering function, the user would have to call a different function depending on which type the object really was, which would defeat our attempt to make them interchangeable.
Susan: But if the two versions of Reorder were exactly the same, couldn't you just declare them public?
Steve: If they were exactly the same, we wouldn't need two functions in the first place. Reordering works slightly differently for dated than for undated items, so we need two different functions to do the "same" thing in two different ways.
Susan: Yes, but if the names were the same couldn't they be used anywhere just by making them public? I thought this was the whole idea: not to have to rewrite these things.
Steve: They are public. The point is that StockItem::Reorder and DatedStockItem::Reorder accomplish the same result in different ways, so the user of these classes should be able to just call Reorder and get the correct function executed without having to worry about which one that is.
Susan: So is it the expiration date that makes it necessary to make a derived class?
Steve: Yes.
Susan: Is it impossible to extend our old class so it can handle objects both with and without expiration dates rather than making a new class?
Steve: No, it is not impossible to do that. But if we did that, we would be defeating one of the main purposes of object-oriented programming: the ability to divide a task up into pieces that can be handled by different classes that can be maintained more easily. To accomplish that goal, we need two different classes to handle these two different kinds of objects.
Susan: Why can't we just add some new member functions and member variables to a class instead of making a derived class? Are you using inheritance here just to make a point, or is it vital to achieve what we want to achieve?
Steve: If you added more functions, then StockItem would not be StockItem as it is, and needs to be, to handle its original task. You could copy the code for StockItem and then change the copy to handle expiration dates, but that would cause serious maintenance problems later if (when) you had to change the code, because you would have to make the changes in both places. Avoiding such problems was one of the main reasons that C++ was invented.
Susan: Okay, so that explains why we shouldn't add more functions to StockItem but not why we shouldn't add any functions to DatedStockItem.
Steve: Because a DatedStockItem should act just like a StockItem but won't if we add new functions.8 Instead, we'll write new versions of the ones we already have, like Reorder.
Susan: I still don't understand why you have to write a new version of Reorder. A DatedStockItem is supposed to act just like a StockItem.
Steve: Yes, it is supposed to act "just like" a StockItem, as far as the user of these classes is concerned. However, that means that DatedStockItem has to do the "same" things as StockItem but do them in a different way; in particular, reordering items is different when you have to send things back because their expiration dates have passed. However, this difference in implementation isn't important to the application program, which can treat DatedStockItems just like StockItems.
Before we get into the details of the Reorder function in the DatedStockItem class, I should explain what I mean by "regular member function". A regular member function is one that is not in any of the following categories:
1. constructor,
2. destructor,
3. assignment operator (operator =).
When we write a derived class (in this case DatedStockItem), it inherits only the regular member functions, not the constructor, destructor, or operator = functions, from the base class (in this case StockItem). Instead, we have to write our own derived class versions of these functions if we don't want to rely on the compiler-generated versions in the derived class.
It may not be obvious why we have to write our own versions of these functions. It wasn't to Susan:
Susan: So in this case our derived class DatedStockitem doesn't inherit the constructor, destructor, and assignment operator because it takes an object of the Stockitem class and combines it with a new member variable m_Expires to make an object of the derived DatedStockItem class. But if the only differences between the two classes are in the implementation of the "regular member functions" then the default constructor, after the inheritance of the base class, should have no problem making a new derived class object because it won't contain any new member variables.
Steve: You're right: that would be possible in such a case, but not in this case, because we are adding a new member variable. However, the code in the base class functions isn't wasted in any event because the base class constructor, destructor, and operator = functions are used automatically in the implementation of the corresponding derived class functions.
Susan: But what if they are similar to the derived class functions that do the same thing? Can't you use them then?
Steve: In the case of the base class constructor and destructor, you actually do use them indirectly; the compiler will always call a base class constructor when a derived class constructor is executed, and it will always call the base class destructor when the derived class destructor is executed.9 Similarly, the derived class assignment operator will call the base class assignment operator to copy the base class part of the derived class object. However, any new member variables added to the derived class will have to be handled in the derived class functions.
Susan: So, anything not derived that is added to a derived class has to be handled as a separate entity from the stuff in the base class part of the derived class? UGH!
Steve: Yes, but just the newly added data has to be handled separately; the inherited data from the base class is handled by the base class functions. Someone has to write the code to handle the new member variables; the compiler can't read our minds!
We won't be wasting any effort when writing the derived class versions of the constructors, destructor, and assignment operator, because the base class versions of these functions are called automatically to construct, destroy, and assign the base class part of the derived class object. Therefore, we can concentrate on the newly added parts of that class. We'll see exactly how and when these base class functions are called as we go through the corresponding derived class functions.10
For the moment, we won't have to define any of these derived class functions except two new constructors. Since the member variables and the base class part of DatedStockItem are all concrete data types, the compiler-generated versions of the destructor and assignment operator, which call the destructors and assignment operators for those member variables (and the base class part), work perfectly well.11
Susan didn't let this "compiler-generated" stuff slip by without a bit of an argument:
Susan: About this statement that "compiler-generated versions of the destructor and assignment operators work perfectly well": Since when were destructors compiler-generated? I thought only assignment operators could be compiler-generated. You are holding out on me.
Steve: Every class, by default, has a default constructor, copy constructor, assignment operator, and destructor. Any of these that we don't mention in our interface will automatically be generated by the compiler.
She also had some questions about concrete data types:
Susan: Aren't the member variables of a class always concrete data types (i.e., variables that act like native data types)? I thought a concrete data type was "a class whose objects behave like native data types".
Steve: Well, if every class defined a concrete data type, we wouldn't need a separate name for that concept, would we? As this suggests, it's entirely possible to have member variables that aren't concrete data types. In particular, pointers aren't concrete data types, because copying them doesn't actually copy their data, only the address of the data to which they refer. That's part of what makes them so tricky to work with.
Before we can use StockItem as a base class, however, there is one change we're going to make to our previous definition of StockItem to make it work properly in that application; namely, we have to change the access specifier for its member variables from private to protected. By this point, you should be familiar with the meaning of private. Any member variables or member functions that are marked private can be referred to only by member functions of the same class; all other functions are denied access to them. On the other hand, when we mark member functions or member data of a class as public, we are specifying that any function, whether or not a member function of the class in question, can access them. That seems to take care of all the possibilities, so what is protected good for?
9.6. The protected Access Specifier
Member variables and member functions that are listed in a protected section of the interface definition are treated as though they were private, with one important exception: member functions of derived classes can access them when they occur as the base class part of a derived class object.
In the current case, we've seen that a DatedStockItem is "just like" a StockItem with one additional member variable and some other additions and changes that aren't relevant here. The important point is that every DatedStockItem contains everything that a StockItem contains. For example, every DatedStockItem has a m_MinimumStock member variable because the StockItem class has a m_MinimumStock member variable, and we're defining DatedStockItem as being derived from StockItem. Logically, therefore, we should be able to access the value of the m_MinimumStock member variable in our DatedStockItem. However, if that member variable is declared as private, we can't, because the private access specifier doesn't care about inheritance. Since DatedStockItem is a different class from StockItem, any private member variables and functions that StockItem might contain wouldn't be accessible to member functions of DatedStockItem, even though the member variables of StockItem are actually present in every DatedStockItem! That's why we have to make those variables protected rather than private.
Susan had some questions about this new concept of protected members:
Susan: I don't understand why, if DatedStockItem has those member variables, it wouldn't be able to access them if they were specified as private in the base class.
Steve: Because the compiler wouldn't let DatedStockItem member functions access them; private variables are private to the class where they are defined (and its friends, if any) even if they are part of the base class part of a derived class object. That's why protected was invented.
Susan: Well, friend lets a class or a function access something in another class, so what's the main difference between protected and friend?
Steve: They're used in different situations. You can use friend when you know exactly what other class or function you want to allow to access your private or protected members. On the other hand, if all you know is that a derived class will need to access these members, you make them protected.
In other words, a protected variable or function is automatically available to any derived class, as it applies to the base class part of the derived class object. To make a class or function a friend to a class being defined, you have to name the friend class or friend function explicitly.
Susan: What do you mean by "base class part of the derived class object"? I'm fuzzy here.
Steve: Every DatedStockItem (derived class) object contains a StockItem (base class) object and can use all of the non-private member functions of StockItem, because DatedStockItem is derived from StockItem. This is what allows us to avoid having to rewrite all the code from the base class in the derived class.
Susan: Yes, but how is that different from just a regular base class object?
Steve: Well, one difference is that a base class part of a derived class object only exists inside a derived class object, not by itself, and must be initialized as part of the derived class object's initialization. A less obvious difference, though, is that protected variables and functions are accessible to derived class objects only when they are part of the base class part of a derived class object of that type, not when they are part of a freestanding base class object. For example, if one of the arguments to DatedStockItem function was a StockItem object, that DatedStockItem function could not access any of the protected members of that StockItem object.
Susan: I don't get this. I need some pictures to clear up these base class and derived class things.
Steve: Okay, I'll give it a shot. Take a look at Figure 9.10, where I've used a dashed-dotted line around the base class, StockItem, to indicate its boundaries as a base class part. I've also used different line types in the boxes around the member variables and functions to indicate the access specifiers of those member variables and functions: a solid box to indicate private members, a dashed box to indicate protected members, and a dotted box to indicate public members. Finally, a solid arrow pointing to a variable indicates the ability to access the variable at the point of the arrow, while the dotted line going from the derived class object to m_UPC indicates that it is inaccessible to the derived class object.
As I told Susan, Figure 9.10 illustrates a hypothetical DatedStockItem class. Here, m_Name, m_Price, and m_InStock are protected base class member variables, whereas m_UPC is a private member variable and GetPrice() is a public member function.12
According to this scenario, the derived class member functions can access m_Name, m_Price, and m_InStock. Of course, any member function can access any public member of any other class, so GetPrice is accessible to DatedStockItem member functions as well. However, with this setup member functions of DatedStockItem could not access m_UPC, even though this member variable would actually be present in the base class part of a DatedStockItem.
Now that we've cleared up that point (I hope), we have to consider the question of when to use protected versus private variables. The private member variables of the base class cannot be accessed directly by derived class member functions. This means that when we define the base class, we have to decide whether we want to allow any derived classes direct access to some of the member variables of the base class part of the derived object. If we do, we have to use the protected access specifier for those member variables. If we make them private and later discover that we need access to those variables in a derived class, we then have to change the definition of the base class so that the variables are protected rather than private. Such changes are not too much trouble when we have written all of the classes involved, but they can be extremely difficult or even impossible when we try to derive new classes from previously existing classes written by someone else.
FIGURE 9.10.A derived class object with its base class part
However, protected members (especially protected variables) have some of the drawbacks of public variables and functions. Anyone can define a new derived class that uses those variables or functions, and any changes to those base class variables or functions will cause the code in the derived class to break. Hence, making everything protected isn't a panacea.
Analogously to our earlier discussion of public member variables versus public member functions, the drawbacks of protected variables are more serious than those of protected member functions, which at least don't commit you to specific implementation details. In the present case, I doubt that there would be any significant difference in maintainability between these two approaches, as we are designing both the base and derived classes. But in a larger project it might very well be better to use protected member functions to allow access to private member variables in the base class rather than to use protected member variables for the same purpose.
The moral of the story is that it's easier to design classes for our own use and derivation than for the use of others. Even though we can go back and change our class definitions to make them more flexible, that alternative may not be available to others. The result may be that our classes will not meet others' needs.
Susan didn't think this conclusion was so obvious.
Susan: I don't get your moral of the story. Sorry.
Steve: The moral is that when designing classes that may be used by others as base classes, we have to know whether those others will ever need access to our member variables. If we are in charge of all of the classes, we can change the access specifiers easily enough, but that's not a very good solution if someone else is deriving new classes from our classes.
Susan: Okay, I guess. But what does that have to do with using protected variables or private ones with protected member functions?
Steve: Only that if we used private variables with protected member functions to access them, we could allow the derived class to use the member variables in our base class in a controlled way rather than an uncontrolled one, and therefore could keep some say in how they are used. Unfortunately, this solution still requires us to figure out how the derived class member functions may want to use our member variables, so it isn't a "silver bullet".
Susan: I still don't understand why we need to worry about who else is going to use our classes; who are these other people?
Steve: One of the main advantages claimed for object-oriented programming is that it allows "division of labor"; that is, some programmers can specialize in building classes while others can specialize in writing application programs. This can increase productivity greatly, just as it does in medicine (e.g., general practitioners, specialists, nurses, and lab technicians).
Susan: Okay, but what does that have to do with access specifiers? Why don't we just make everything public and avoid all this stuff?
Steve: If we are going to let others use our classes, we should design them to be easy to use correctly and hard to use incorrectly. That's one of the main reasons we use private and protected: so we can determine where in our program an error might be caused. If we notice that one of our private member variables is being changed when it shouldn't be, we know where to look: in the code that implements the class. Because the member variable is private, we don't have to worry that it's being changed somewhere else. This is not the case with a public member variable, which can be modified anywhere in the program. If you'd ever had to try to find out where a variable is being modified in a gigantic program in C or another language that doesn't have private variables, you would know exactly what I mean!
After that excursion into the use of the protected access specifier and its impact on class design, let's look at the revised test program in Figure 9.11.
FIGURE 9.11. The next version of the inventory control test program (code\itmtst21.cpp)
ifstream ShopInfo("shop21.in");
ofstream ReorderInfo("shop21.reo");
MyInventory.LoadInventory(ShopInfo);
MyInventory.ReorderItems(ReorderInfo);
The new test program, Itmtst21, is exactly the same as its predecessor, itmtst20.cpp, except that it #includes the new header files item21.h and invent21.h (shown in Figures 9.12 and 9.13, respectively) and uses different input and output file names.13
Assuming that you've installed the software from the CD in the back of this book, you can try out this program. First, you have to compile it by following the compilation instructions on the CD. Then type itmtst21 to run the program. However, this program doesn't have much of a user interface, so you might want to watch it operating under the debugger by following the usual instructions for that method. When the program terminates, you can look at its output file, shop21.reo, to see what the reorder report looks like; if you do, you will see that it includes instructions to return some expired items.
Now that we've seen the results of using the new versions of our inventory control classes, let's take a look at the interface definitions of StockItem and DatedStockItem (Figure 9.12) as well as the implementation of those classes (Figure 9.13). I strongly recommend that you print out the files that contain these interfaces and their implementation for reference as you go through this section of the chapter; those files are code\item21.h and code\item21.cpp, respectively.14
Susan had some questions about where the new class interface and implementation were defined:
Susan: So you just write your new class right there? I mean you don't start over with a new page or something; shouldn't it be a different file or coded off all by itself somewhere? How come it is where it is?
Steve: We could put it in another file, but in this case the classes are being designed together and are intended to be used interchangeably in the application program, so it's not unreasonable to have them in the same file. In other circumstances, it's more common to have the derived class in a separate file. Of course, sometimes you don't have any choice, such as when you're deriving a new class from a class that you didn't create in the first place and may not even have the source code for; in that case, you have to create a separate file for the derived class.
FIGURE 9.12. Full interface for StockItem and DatedStockItem (code\item21.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(std::string Name, short InStock,
short Price, short MinimumStock,
short MinimumReorder, std::string Distributor, std::string UPC);
bool CheckUPC(std::string ItemUPC);
void DeductSaleFromInventory(short QuantitySold);
short GetInventory();
std::string GetName();
std::string GetUPC();
bool IsNull();
short GetPrice();
void Reorder(std::ostream& os);
void FormattedDisplay(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 StockItem
{
friend std::ostream& operator << (std::ostream& os,
const DatedStockItem& Item);
friend std::istream& operator >> (std::istream& is, DatedStockItem& Item);
public:
DatedStockItem();
DatedStockItem(std::string Name, short InStock, short Price,
short MinimumStock, short MinimumReorder,
std::string Distributor, std::string UPC, std::string Expires);
void FormattedDisplay(std::ostream& os);
void Reorder(std::ostream& os);
protected:
static std::string Today();
protected:
std::string m_Expires;
};
FIGURE 9.13. Latest implementation of StockItem class and first implementation of DatedStockItem class (code\item21.cpp)
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <dos.h>
#include "item21.h"
using namespace std;
StockItem::StockItem()
: m_InStock(0), m_Price(0), m_MinimumStock(0),
m_MinimumReorder(0), m_Name(), m_Distributor(),
m_UPC()
{
}
StockItem::StockItem(string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC)
: m_InStock(InStock), m_Price(Price),
m_MinimumStock(MinimumStock),
m_MinimumReorder(MinimumReorder), m_Name(Name),
m_Distributor(Distributor), m_UPC(UPC)
{
}
void StockItem::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 << "Minimum Reorder quantity: ";
os << m_MinimumReorder << endl;
os << "Distributor: ";
os << m_Distributor << endl;
os << "UPC: ";
os << m_UPC << endl;
}
ostream& operator << (ostream& os, const StockItem& Item)
{
os << Item.m_Name << endl;
os << Item.m_InStock << endl;
os << Item.m_Price << endl;
os << Item.m_MinimumStock << endl;
os << Item.m_MinimumReorder << endl;
os << Item.m_Distributor << endl;
os << Item.m_UPC << endl;
return os;
}
istream& operator >> (istream& is, StockItem& Item)
{
getline(is,Item.m_Name);
is >> Item.m_InStock;
is >> Item.m_Price;
is >> Item.m_MinimumStock;
is >> Item.m_MinimumReorder;
is.ignore();
getline(is,Item.m_Distributor);
getline(is,Item.m_UPC);
return is;
}
bool StockItem::CheckUPC(string ItemUPC)
{
if (m_UPC == ItemUPC)
return true;
return false;
}
void StockItem::DeductSaleFromInventory(short QuantitySold)
{
m_InStock -= QuantitySold;
}
short StockItem::GetInventory()
{
return m_InStock;
}
string StockItem::GetName()
{
return m_Name;
}
string StockItem::GetUPC()
{
return m_UPC;
}
bool StockItem::IsNull()
{
if (m_UPC == "")
return true;
return false;
}
short StockItem::GetPrice()
{
return m_Price;
}
void StockItem::Reorder(ostream& os)
{
short ActualReorderQuantity;
if (m_InStock < m_MinimumStock)
{
ActualReorderQuantity = m_MinimumStock - m_InStock;
if (m_MinimumReorder > ActualReorderQuantity)
ActualReorderQuantity = m_MinimumReorder;
os << "Reorder " << ActualReorderQuantity;
os << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " from " << m_Distributor << endl;
}
}
DatedStockItem::DatedStockItem()
: m_Expires()
{
}
string DatedStockItem::Today()
{
date d;
short year;
char day;
char 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 Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC, string Expires)
: StockItem(Name, InStock, Price, MinimumStock,
MinimumReorder, Distributor, UPC),
m_Expires(Expires)
{
}
void DatedStockItem::Reorder(ostream& os)
{
if (m_Expires < Today())
{
os << "Return " << m_InStock;
os << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " to " << m_Distributor << endl;
m_InStock = 0;
}
StockItem::Reorder(os);
}
void DatedStockItem::FormattedDisplay(ostream& os)
{
os << "Expiration Date: ";
os << m_Expires << endl;
StockItem::FormattedDisplay(os);
}
ostream& operator << (ostream& os, const DatedStockItem& Item)
{
os << Item.m_Expires << endl;
os << Item.m_Name << endl;
os << Item.m_InStock << endl;
os << Item.m_Price << endl;
os << Item.m_MinimumStock << endl;
os << Item.m_MinimumReorder << endl;
os << Item.m_Distributor << endl;
os << Item.m_UPC << endl;
return os;
}
istream& operator >> (istream& is, DatedStockItem& Item)
{
getline(is,Item.m_Expires);
getline(is,Item.m_Name);
is >> Item.m_InStock;
is >> Item.m_Price;
is >> Item.m_MinimumStock;
is >> Item.m_MinimumReorder;
is.ignore();
getline(is,Item.m_Distributor);
getline(is,Item.m_UPC);
return is;
}
Before we get to the new implementation for these classes in item21.cpp, I should mention the new header file #included in item21.cpp: <dos.h>. This header file defines a data type needed by the Today function, which we'll get to in a little while.
If you're writing programs to run under another operating system such as Unix`, I should warn you that <dos.h>, as its name suggests, is specific to Microsoft` operating systems.15 Therefore, this program won't compile in its current form under other operating systems. While this is a soluble problem, the solution is outside the scope of this book.
Now let's get to the interface definition for DatedStockItem. Most of this should be pretty simple to follow by now. We have to declare new versions of operator << and operator >>, which will allow us to write and read objects of the DatedStockItem class as we can already do with the normal StockItem. As before, the friend specifiers are needed to allow these global input and output functions to access the internal variables of our class.
Susan wanted to know why we had to write new operators << and >> for the DatedStockItem class when we had already written them for its base class, StockItem.
Susan: Why can't DatedStockItem use the same >> and << that StockItem uses? If it's derived from StockItem, it should be able to use the same ones. I don't get it.
Steve: It can't use the same ones because a DatedStockItem has a new member variable that the StockItem I/O operators don't know about.
Susan: But why can't the compiler do it for us?
Steve: Because the compiler doesn't know how we want to display the data when we're writing it or input the data when we're reading it. For example, when displaying the data, should it put an endl after each member variable or run them all together on one line? Should it even display all of the member variables? Maybe there are some that the user of the class doesn't care about. In some cases, the real data for the class isn't even contained in its objects, as we'll see in Chapter 10. Therefore, we have to write operator << ourselves.
Then we have the default constructor, DatedStockItem(), and the "normal" constructor that supplies values for all of the member variables. We also have to declare the Reorder function we are writing for this class.
Although all of the preceding function declarations should be old hat by now, there are a couple of constructs here that we haven't seen before. The first one is in the class header: DatedStockItem: public StockItem. I'm referring specifically to the expression : public StockItem, which states that the new class being defined, in this case DatedStockItem, is publicly derived from StockItem. We have discussed the fact that deriving a new class from an old one means that the new class has everything in it that the old class had in it. But what does the public keyword mean here?
public Inheritance
It means that we are going to allow a DatedStockItem to be treated as a StockItem; that is, any function that takes a StockItem as a parameter will accept a DatedStockItem in its place. As this implies, all of the public member functions and public data items (if there were any) in the base class (StockItem in this case) are publicly accessible in an object of the derived class (DatedStockItem in this case) object as well. This is called, imaginatively enough, public inheritance.
The relationship between a base class and a publicly derived class is commonly expressed by saying that the derived class "isA" base class object.16
private Inheritance
You might be wondering whether there are other types of inheritance besides public. The answer is that there is another type that is useful sometimes: private. If we wrote : private StockItem rather than : public StockItem as the base class specification for DatedStockItem, DatedStockItem member functions would still be able to use the protected and public member variables and member functions of StockItem in their implementation, just as with public inheritance. However, the fact that DatedStockItem is derived from StockItem would not be apparent to any outside function. That is, if we specified private rather than public inheritance, a DatedStockItem would not be an acceptable substitute for a StockItem; alternatively, we could say that DatedStockItem would not have an "isA" relationship with StockItem. The primary use for private inheritance is to reuse the implementation of an existing class while being able to modify some of its behavior.17 Other than that, private inheritance is not particularly useful, and we won't be using it in this book.
Susan had a number of questions about the uses of inheritance.
Susan: I don't understand this idea of substituting one type of object for another. Why don't you decide which kind of object you want and use that one?
Steve: The StockItem and DatedStockItem classes are a good example of why we would want to be able to substitute one type of object for another. To the user of these classes, they appear the same except that when we want to create a DatedStockItem, we need one more item of information (the expiration date) and that type of object produces a slightly different reordering report. Therefore, being able to treat these two classes in the same way makes it much easier to write a program using them, because the user doesn't need a lot of code saying, "if it's a DatedStockItem, do this; if it's a StockItem, do something else".
Susan: Okay, but if you use : private StockItem, then how come it can use the protected and public parts of StockItem and not just the private parts? I just don't get this at all.
Steve: That's understandable, because this is another confusing case of C++ keyword abuse: the private keyword in the class declaration line means something different from its meaning in the class definition. In the class declaration line, it means that no user of the class can treat an object of the derived class being declared as a substitute for a base class object. In other words, what is private is the inheritance from the base class, not the variables in the base class. It's sort of like an orphan left on a doorstep; the derived class has the DNA of the base class, but not the family tree.
This substitutability of an object of a publicly derived class (e.g., a DatedStockItem) for an object of its base class (e.g., StockItem) extends to areas where its value is somewhat questionable. In particular, a derived class object can be assigned to a base class object; for example, if x is a StockItem and y is a DatedStockItem, the statement x = y; is legal. The result will be that any member variables that exist in the derived class object but not in the base class object will be lost in the assignment. In our example, after the statement x = y;, x will contain all the member variables of y except for m_Expires, which is not present in the base class. This "partial assignment" is called slicing and it can be a serious annoyance because the compiler won't warn you that it's taking place. After all, since a DatedStockItem "isA" StockItem, it's perfectly legal to assign an object of the former class to an object of the latter class, even if that isn't what you had in mind. However, you shouldn't worry about this problem too much because as we'll see in the next chapter, we can solve it by using more advanced techniques.18
Before we get into the implementation of the DatedStockItem class, let's take a look at the other new construct in its interface: a static member function. I'll give you a hint as to its meaning: in another case of the grand old C/C++ tradition of keyword abuse, the meaning of static here is almost, but not quite, entirely unlike its meaning for either local or global variables.
9.7. static Member Functions
Give up? Okay. When we declare a member function to be static, we don't have to specify an object when we call the member function. Thus, we can refer to the static member function Today by its name followed by empty parentheses to indicate a function call. Within DatedStockItem member functions, writing "Today();" is sufficient. Of course, if Today were public, and we wanted to call it from a nonmember function, we would have to refer to it by its full name: DatedStockItem::Today(). Either of these calls differs from the normal use of a member function, where we specify the function along with the object to which it applies - for example, in the expression "soup.GetPrice();".
That explains what the static modifier does, but why would we want to use it? Because some member functions don't apply to any particular object, it is convenient to be able to call such a function without needing an object with which to call it. In the case of the Today function, the value of today's date is not dependent on any DatedStockItem object; therefore, it makes sense to be able to call Today without referring to any object of the DatedStockItem class.
At this point, Susan had a flash of insight about the utility of static member functions:
Susan: I just realized that this way of writing functions is sort of like writing a path; it tells the compiler where to go to find things - is that right?
Steve: Right. The reason that we make this a member function is to control access to it and to allow it to be used by this class, not because it works on a particular class object (as is the case with non-static member functions).
Susan: So, is using this static thing like making it a default?
Steve: Sort of, because you don't have to specify an object for the function to act on.
Of course, we could also avoid having to pass an object to the Today function by making it a global function. However, the advantages of using a static protected member function rather than a global one are much the same as the advantages of using private rather than public member variables. First, we can change the interface of this function more easily than that of a global function, as we know that it can be accessed only by member functions of DatedStockItem and any possible derived classes of that class, not by any function anywhere. Second, we don't have to worry that someone else might want to define a different function with the same signature, which could be a problem with a global function. The full name of this function, DatedStockItem::Today(), is sufficient to distinguish it from any other Today functions that belong to other classes, or even from a global function of that name, should another programmer be so inconsiderate as to write one!
There's one other thing here that we haven't seen before: Today is a protected member function, which means that it is accessible only to member functions of DatedStockItem and its descendants, just as a protected member variable is. We want to keep this function from being called by application programs for the same reason that we protect member variables by restricting access: to reserve the right to change its name, return value, or argument types. Application code can't access this function and therefore can't depend on its interface.
Susan had some questions about changing the Today function as well as about the more general idea of many programmers working on the same program.
Susan: Why would we want to change the Today function? It seems like it would work fine the way it is.
Steve: Well, we might decide to make it return a number rather than a string, if we changed the way we implemented our date comparisons. But the point is more general: the fewer people who know about a particular function, the easier it will be to make changes to its interface.
Susan: Who are these other people you're always talking about? I thought a programmer wrote his own programs.
Steve: That all depends. Some small projects are done by a single programmer, which might seem to make access specifiers redundant. But they really aren't, even in that case, because a lone programmer puts on different "hats" while writing a significant program. Sometimes he's a class designer and sometimes an application programmer.
But where these design considerations are really important is in big projects, which may be written by dozens or even hundreds of programmers. In such cases, the result of letting everyone access every variable or function can be summed up in one word: chaos. Such free-for-alls have led to a lot of buggy software.
Figure 9.14 is the implementation of the protected static member function DatedStockItem::Today.
FIGURE 9.14. DatedStockItem::Today() (from code\item21.cpp)
string DatedStockItem::Today()
{
date d;
short year;
char day;
char 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;
}
Here's where we use the date type defined in the line #include <dos.h> in Figure 9.13. As its name suggests, a date is used to store the components of a date (i.e., its month, day, and year). Now that we've gotten that detail out of the way, let's look at this Today function. First, we have to call the getdate function (whose declaration is also in <dos.h>) to ascertain the current date; getdate handles this request by filling in the member variables in a variable of type date. Note that the argument to the getdate function is the address of the date variable (i.e., &d) rather than the variable itself.
This is the first time we've seen & used in this way, rather than as an indication of a reference argument or return type. Unfortunately, & is one of the tokens that is used in several different ways depending on context; when it precedes the name of a variable without itself being preceded by a data type like string, it means "the address of the following variable". In this case, &d means "the address of d".
But why do we have to pass the address of d to the getdate function in the first place, rather than providing it as a reference argument that can be changed by getdate? Because the getdate function is left over from C, which doesn't have reference variables. Since all C arguments are value arguments, a C function can't change any of its arguments. C programmers get around this limitation by giving the called function the address of the variable to be modified; then the called function uses that address as a pointer to the actual variable. Happily, we don't have to concern ourselves about this in any more detail than I've just mentioned.19
By the way, this is a good example of the difference between calling a member function and calling a nonmember function. We have to specify the address of the date variable d as an argument when calling getdate because getdate isn't a member function of the date type; this is because C doesn't have member functions. Of course, with a member function, the compiler automatically supplies the this pointer to every (non-static) member function as a hidden argument, so we don't have to worry about it.
This example finally convinced Susan of the value of this:
Susan: OK, I finally see why making a hidden argument of this makes sense. Otherwise, you'd have to pass the object's address to all the member functions yourself.
Steve: Right!
After we call getdate, the current year is left in the da_year member variable of the date variable d, and the current day and month are left in the other two member variables, da_day and da_mon. Now that we have the current year, month, and day, the next step is to produce a string that has all of these data items in the correct order and format. To do this, we use some functions from the iostream library that we haven't seen before. While these functions are very convenient, we can't call them unless we have a stream of some sort for them to work with.
So far, we've used istream and ostream objects, but neither of those will do the job here. We don't really want to do any input or output at all; we just want to use the formatting functions that streams provide. Since this is a fairly common requirement, the implementers of the iostream library have anticipated it by supplying stringstream.
A stringstream is a stream that exists entirely in memory rather than as a conduit to read or write data. In this case, we've declared a stringstream called FormatStream, to which we'll write our data. When done, we'll read the formatted data back from FormatStream.
This discussion assumes that you're completely comfortable with the notion of a stream, which may not be true. It certainly wasn't for Susan, as the following indicates:
Susan: I don't understand your definition for stringstream. Why is FormatStream a part of stringstream instead of just from the iostream library? When does it exist in memory? When is it called to work? How is it different from the "conduit" type of streams? I am not understanding this because I told you that I don't understand what a stream really is. So what does stringstream really do?
Let's see if we can answer her questions (and possibly yours) by taking a closer look at streams.
9.8. The stream classes
A stream is a facility that allows us to use various input and output devices more or less uniformly. There are a number of variants of this data type, which are related by inheritance so that we can substitute a more highly specialized variant for a more basic one. So far we've encountered istream, ostream, ifstream, ofstream, and of course most recently stringstream. The best place to start a further investigation of this family of classes is with one of the simplest types, an ostream. We've used a predefined object of this type, namely cout, quite a few times already. To see a little bit more about how this works, take a look at the program in Figure 9.15.
FIGURE 9.15. A simple stream example (code\stream1.cpp)
#include <iostream>
using namespace std;
int main()
{
short x;
char y;
x = 1;
y = `A';
cout << "Test " << x;
cout << " grade: " << y;
cout << endl;
return 0;
}
When the program starts, cout looks something like Figure 9.16.
FIGURE 9.16.An empty ostream object
What is the purpose of the buffer and the put pointer?20 Here's a breakdown:
1. The buffer is the area of memory where the characters put into the ostream are stored.
2. The put pointer holds the address of the next byte in the output area of the ostream - that is, where the next byte will be stored if we use << to write data into the ostream.
At this point, we haven't put anything into the ostream yet, so the put pointer is pointing to the beginning of the buffer. Now, let's execute the line cout << "Test " << x;. After that line is executed, the contents of the ostream look something like Figure 9.17.
FIGURE 9.17. An ostream object with some data![]()
As you can see, the data from the first output line has been put into the ostream buffer. After the next statement, cout << " grade: " << y;, is executed, the ostream looks like Figure 9.18.
FIGURE 9.18. An ostream object with some more data.![]()
Now we're ready for the final output statement, cout << endl;. Once this statement is executed, the ostream looks like Figure 9.19.
By now, you're probably wondering what happened to all the data we stored in the ostream. It went out to the screen because that's what endl does (after sticking a newline character on the end of the buffer). Once the data has been sent to the screen, we can't access it anymore in our program, so the space that it took up in the buffer is made available for further use.
FIGURE 9.19. An empty ostream object![]()
9.9. More about stringstream
Now it's time to get back to our discussion of stringstream. A stringstream is very similar to an ostream, except that once we have written data to a stringstream, we can read it from the stringstream into a variable just as though we were reading from a file or the keyboard. An example is the program shown in Figure 9.20, which uses a stringstream object to combine a year, month, and day number to make one string containing all of those values.
FIGURE 9.20. A stringstream formatting example (code\stream2.cpp)
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
stringstream FormatStream;
string date;
short year = 1996;
short month = 7;
short day = 28;
FormatStream << year;
FormatStream << month;
FormatStream << day;
FormatStream >> date;
cout << "date: " << date << endl;
return 0;
}
Figure 9.21 shows what an empty stringstream looks like.
FIGURE 9.21. An empty stringstream object![]()
We've discussed the put pointer and the buffer, but what about the get and end pointers? Here's what they're for:
1. The get pointer holds the address of the next byte in the input area of the stream, or the next byte we get if we use >> to read data from the stringstream.
2. The end pointer indicates the end of the stringstream. Attempting to read anything at or after this position will cause the read to fail because there is nothing else to read.
You probably won't be surprised to learn that Susan wasn't that thrilled with all these new kinds of pointers, or with streams in general for that matter.
Susan: The only thing that can be worse than pointers is different kinds of pointers. How are get and end different from other kinds of pointers? Ick.
Steve: They are effectively the addresses of the current places in the buffer where characters will be read and written, respectively. Since they are not directly accessible to the programmer, their actual representation is irrelevant; all that matters is how they work.
Susan: You just have no idea how hard this stream stuff is.
Steve: Why is it any harder than cout? It's just the same, except that the actual destination may vary.
Susan: No, there is no cout when we're using these other streams.
Steve: Yes, but that's the only difference between writing to a stringstream and writing to cout, which never bothered you before.
Susan: But I can see the screen; I can't see these other things.
Steve: Yes, but you can't see the stream in either case. Anyway, don't the diagrams help?
Susan: Yes, they help but it still isn't the same thing as cout. Anyway, I'm not really sure at any given time exactly where the data really is. Where are these put, get, and end pointers?
Steve: They're stored in the stringstream object as part of its member data, just as m_Name and the other member variables are stored in a StockItem object.
Susan: Okay, but what about the buffer?
Steve: That's in an area of memory allocated for that purpose by the stringstream member functions. The streams classes use the get and put pointers to keep track of the exact position of the data in the buffer, so we don't have to worry about it.
Susan: I still don't like it. It's not like cout.
Steve: Yes, but cout is just an ostream, and a stringstream is just like an ostream and an istream combined; you can write to it just like an ostream and then read back from it just like an istream.
Susan: I still think streams are going to be my downfall. They're just too vague and there seem to be so many of them. I know you say that they are good, and I believe you, but they are still a little mysterious. This is not going to be the last you've heard of this.
Steve: It seems to me that you've managed to survive lots of other new concepts. I suspect streams won't be any exception.
Now let's get back to our diagrams of streams. After the statement FormatStream << year;, the stringstream might look like Figure 9.22.
FIGURE 9.22. A stringstream object with some contents![]()
The put pointer has moved to the next free byte in the stringstream, but the get pointer hasn't moved because we haven't gotten anything from the stringstream.
The next statement is FormatStream << month;, which leaves the stringstream looking like Figure 9.23.
FIGURE 9.23. A stringstream object with some more contents![]()
After we execute the statement FormatStream << day;, the stringstream looks like Figure 9.24.
FIGURE 9.24. A stringstream object with even more contents![]()
Now it's time to get back what we put into the stringstream. That's the job of the next statement, FormatStream >> date;. Afterward the variable date has the value "1996728" and the stringstream looks like Figure 9.25.
FIGURE 9.25. A stringstream object after reading its contents![]()
In other words, we've read to the end of the stringstream, so we can't read from it again until we "reset" it. This shouldn't seem too strange, as it is exactly analogous to what happens when we've read all of the data in a file through an ifstream, which causes the next read to "fail".21
In these diagrams, the end and put pointers always point to the same place, so why do we need both? Because we can reset the put pointer to any place in the stringstream before the current end pointer and write over the data that has already been written. We don't make use of that facility in this book, but it's there when needed.
Controlling Output Formatting
Now let's get back to the problem of converting a date to a string so that we can compare it with another date. You might think that all we have to do is write each data item out to the stringstream and then read the resulting formatted data back in, as in the program in Figure 9.20. However, it's a bit more complicated than that, because in order to compare two of these values correctly we need to control the exact format in which the data will be written. To see why this is necessary, consider the program shown in Figure 9.26.
FIGURE 9.26. Default formatting example (code\coutdef1.cpp)
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
stringstream FormatStream1;
stringstream FormatStream2;
string date1;
string date2;
short year1 = 1996;
short month1 = 12;
short day1 = 28;
short year2 = 1996;
short month2 = 7;
short day2 = 28;
FormatStream1 << year1 << month1 << day1;
FormatStream1 >> date1;
FormatStream2 << year2 << month2 << day2;
FormatStream2 >> date2;
cout << "date1: " << date1 << ", date2: " << date2 << endl;
if (date1 < date2)
cout << "date1 is less than date2" << endl;
else if (date1 == date2)
cout << "date1 is the same as date2" << endl;
else
cout << "date1 is greater than date2" << endl;
return 0;
}
The output of that program is shown in Figure 9.27.
FIGURE 9.27. Output of default formatting example (code\coutdef1.out)
date1: 19961228, date2: 1996728
What's wrong with this picture? Well, the string comparison of the first value with the second shows that the first is less than the second. Clearly this is wrong, since the date the first string represents is later than the date the second string represents. The problem is that we're not formatting the output correctly; what we have to do is make month numbers less than 10 come out with a leading 0 (e.g., July as 07 rather than 7). The same consideration applies to the day number; we want it to be two digits in every case. Of course, if we knew that a particular number was only one digit, we could just add a leading 0 to it explicitly, but that wouldn't work correctly if the month or day number already had two digits.
To make sure the output is correct without worrying about how many digits the value originally had, we can use iostream member functions called manipulators, which are defined not in <iostream> but in another header file called <iomanip>. This header file defines setfill, setw, and a number of other manipulators that we don't need to worry about at the moment. These manipulators operate on fields; a field can be defined as the result of one << operator. In this case, we use the setw manipulator to specify the width of each field to be formatted, and the setfill manipulator to set the character to be used to fill in the otherwise empty places in each field.
Susan had some questions about why we're using manipulators here.
Susan: Why are manipulators needed? Why can't you just add the 0 where it is needed?
Steve: Well, to determine that, we'd have to test each value to see whether it was large enough to fill its field. It's a lot easier just to use setw and setfill to do the work for us.
Let's change our example program to produce the output we want, as shown in Figure 9.28.
FIGURE 9.28. Output of controlled formatting example (code\coutdef2.out)
date1: 19961228, date2: 19960728
Using iostream Manipulators
The new program is shown in Figure 9.29. Let's go over how it works. To start with, setfill takes an argument specifying the char that will be used to fill in any otherwise unused positions in an output field. We want those unused positions to be filled with `0' characters so that our output strings will consist entirely of numeric digits.22
FIGURE 9.29. Controlled formatting example (code\coutdef2.cpp)
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
using namespace std;
int main()
{
stringstream FormatStream1;
stringstream FormatStream2;
string date1;
string date2;
short year1 = 1996;
short month1 = 12;
short day1 = 28;
short year2 = 1996;
short month2 = 7;
short day2 = 28;
FormatStream1 << setfill(`0') << setw(4) <<
year1 << setw(2) << month1 << setw(2) << day1;
FormatStream1 >> date1;
FormatStream2 << setfill(`0') << setw(4) <<
year2 << setw(2) << month2 << setw(2) << day2;
FormatStream2 >> date2;
cout << "date1: " << date1 << ", date2: " << date2 << endl;
if (date1 < date2)
cout << "date1 is less than date2" << endl;
else if (date1 == date2)
cout << "date1 is the same as date2" << endl;
else
cout << "date1 is greater than date2" << endl;
return 0;
}
The setfill manipulator, like most iostream manipulators, is "sticky"; that is, it applies to all of the fields that follow it in the same stream.23 However, this is not true of setw, which sets the minimum width (i.e., the minimum number of characters) of the next field. Hence, we need three setw manipulators, one for each field in the output. The year field is four digits, while the month and day fields are two digits each.
Now let's get back to our DatedStockItem::Today function. Once we understand the manipulators we're using, it should be obvious how we can produce a formatted value on our stringstream, but how do we get it back?
That turns out to be easy. Since the get pointer is still pointing to the beginning of the stringstream, the statement FormatStream >> TodaysDate; reads data from the stringstream into a string called TodaysDate just as if we were reading data from cin or a file.
Susan had a question about the statement that reads the data back from the stringstream.
Susan: How do you know that FormatStream >> TodaysDate will get your data back?
Steve: Because that's how stringstreams work. You write data to them just like you write to a regular ostream, then read from them just like you read from a regular istream. They're really our friends!
Base class Constructors
Now that we've taken care of the new function, Today, let's take a look at some of the other functions of the DatedStockItem class that differ significantly from their counterparts in the base class StockItem, the constructors and the Reorder function.24
We'll start with the default constructor, which of course is called DatedStockItem::DatedStockItem() (Figure 9.30). It's a very short function, but there's a bit more here than meets the eye.
FIGURE 9.30. Default constructor for DatedStockItem (from code\item21.cpp)
DatedStockItem::DatedStockItem()
: m_Expires()
{
}
A very good question here is what happens to the base class part of the object. This is taken care of by the default constructor of the StockItem class, which will be invoked by default to initialize that part of this object.
Susan had some questions about this notion of constructing the base class part of an object:
Susan: I don't understand your good question. What do you mean by base class part?
Steve: The base class part is the embedded base class object in the derived class object.
Susan: So derived classes use the default constructor from the base classes?
Steve: They always use some base class constructor to construct the base class part of a derived class object. By default, they use the default constructor for the base class object, but you can specify which base class constructor you want to use.
Susan: If that is so, then why are you writing a constructor for DatedStockItem?
Steve: Because the base class constructor only constructs the base class part of a derived class object (such as DatedStockItem). The rest of the derived class object has to be constructed too, and that job is handled by the derived class constructor.
The following is a general rule: Any base class part of a derived class object will automatically be initialized when the derived object is created at run time, by a base class constructor. By default, the default base class constructor will be called when we don't specify which base class constructor we want to execute. In other words, the code in Figure 9.30 is translated by the compiler as though it were the code in Figure 9.31.
The line : StockItem(), specifies which base class constructor we want to use to initialize the base class part of the DatedStockItem object. This is a construct called a base class initializer, which is the only permissible type of expression in a member initialization list other than a member initialization expression. In this case, we're calling the default constructor for the base class, StockItem.
FIGURE 9.31. Specifying the base class constructor for a derived class object
DatedStockItem::DatedStockItem()
Susan wanted a refresher on the idea of a member initialization list.
Susan: What's a member initialization list again? I forget.
Steve: It's a set of expressions that you can specify before the opening { of a constructor. These expressions are used to initialize the member variables of the object being constructed and (in the case of a derived class object) the base class part of the object.
After clearing that up, Susan wanted to make sure that she understood the reason for the base class initializer, which led to the following exchange:
Susan: Okay, let me see if I can get this straight. The derived class will use the base class default constructor unless you specify otherwise.
Steve: Correct so far.
Susan: But to specify it, you have to use a base class initializer?
Steve: Right. If you don't want the base class part to be initialized with the base class default constructor, you have to use a base class initializer to specify which base class constructor you want to use. Some base class constructor will always be called to initialize the base class part; the only question is which one.
Susan: It just doesn't "know" to go to the base class as a default unless you tell it to?
Steve: No, it always goes to the base class whether or not you tell it to; the question is which base class constructor is called.
Susan: OK, then say that the base class initializer is necessary to let the compiler know which constructor you are using to initialize the base class. This is not clear.
Steve: Hopefully, we've clarified it by now.
Whether we allow the compiler to call the default base class constructor automatically, as in Figure 9.30, or explicitly specify the default base class constructor, as in Figure 9.31, the path of execution for the default DatedStockItem constructor is as illustrated in Figure 9.32.
FIGURE 9.32. Constructing a default DatedStockItem object![]()
At step 1, the DatedStockItem constructor calls the default constructor for StockItem. In step 2, the default constructor for StockItem initializes all the variables in the StockItem class to their default values. Once the default constructor for StockItem is finished, in step 3, it returns to the DatedStockItem constructor. In step 4, that constructor finishes the initialization of the DatedStockItem object by initializing m_Expires to the default string value ("").
This is fine as long as the base class default constructor does the job for us. However, if it doesn't do what we want, we can specify which base constructor we wish to call, as shown in the "normal" constructor for the DatedStockItem class (Figure 9.33).
FIGURE 9.33. Normal constructor for DatedStockItem (from code\item21.cpp)
DatedStockItem::DatedStockItem(string Name, short InStock,
short Price, short MinimumStock, short MinimumReorder,
string Distributor, string UPC, string Expires)
: StockItem(Name, InStock, Price, MinimumStock,
MinimumReorder, Distributor, UPC),
m_Expires(Expires)
{
}
The member initialization expression "StockItem(Name, InStock, Price, MinimumStock, MinimumReorder, Distributor, UPC)" specifies which base class constructor will be used to initialize the base class part of the DatedStockItem object. This base class initializer specifies a call to the "normal" constructor for the base class, StockItem. Thus, the StockItem part of the DatedStockItem object will be initialized exactly as though it were being created by the corresponding constructor for StockItem. Figure 9.34 illustrates how this works.
At step 1, the DatedStockItem constructor calls the "normal" constructor for StockItem. In step 2, the "normal" constructor for StockItem initializes all the variables in the StockItem class to the values specified in the argument list to the constructor. Once the "normal" constructor for StockItem is finished, in step 3, it returns to the DatedStockItem constructor. In step 4, that constructor finishes the initialization of the DatedStockItem object by initializing m_Expires to the value of the argument Expires.
As you can see by these examples, using a base class initializer calls an appropriate base class constructor to initialize the base class part of an object, which in turn means that our derived class constructor won't have to keep track of the details of the base class.
FIGURE 9.34.Constructing a DatedStockItem object
This is an example of one of the main benefits claimed for object-oriented programming; we can confine the details of a class to the internals of that class. In this case, after specifying the base class initializer the only remaining task for the DatedStockItem constructor is to initialize the member variable m_Expires.
Susan wasn't overwhelmed by the simplicity of this notion:
Susan: How does all the information of step 3 get into step 4? And exactly what part of the code here is the base class initializer? I don't see which part it is.
Steve: The information from 3 gets into the derived class DatedStockItem object in the upper part of the diagram because the DatedStockItem object contains a base class part consisting of a StockItem object. That's the object being initialized by the call to the base class constructor caused by the base class initializer, which consists of the following two lines:
: StockItem(Name,InStock,Price,MinimumStock,
MinimumReorder, Distributor,UPC)
There's another reason besides simplicity for using a base class initializer rather than setting the values of the member variables of the base class part of our object directly; it's much safer. If we set the values of the base class variables ourselves, and if the base class definition were later changed to include some new variables initialized according to arguments to the normal constructor, we might very well neglect to modify our derived class code to set the values of the new variables. On the other hand, let's assume that we used a base class initializer; then, if its arguments changed later (as they presumably would if new variables needed to be initialized), a derived class constructor that called that base class initializer would no longer compile. That would alert us to the change we would have to make.
The Reorder Function for the DatedStockItem class
Now that we have dealt with the constructors, let's take a look at the Reorder function (Figure 9.35).
FIGURE 9.35. The Reorder function for DatedStockItem (from code\item21.cpp)
void DatedStockItem::Reorder(ostream& os)
{
if (m_Expires < Today())
{
os << "Return " << m_InStock;
os << " units of " << m_Name;
os << " with UPC " << m_UPC;
os << " to " << m_Distributor << endl;
m_InStock = 0;
}
StockItem::Reorder(os);
}
We have added a new piece of code that checks whether the expiration date on the current batch of product is before today's date; if that is the case, we set the stock on hand to 0 and create an output line indicating the amount of product to be returned. But what about the "normal" case already dealt with in the base class Reorder function? That's taken care of by the line StockItem::Reorder(os);, which calls the StockItem::Reorder function, using the class name with the membership operator :: to specify the exact Reorder function we want to use. If we just wrote Reorder(os), that would call the function we're currently executing, a process known as recursion. Recursion has its uses in certain complex programming situations, but in this case, of course, it would not do what we wanted, as we have already handled the possibility of expired items. We need to deal with the "normal" case of running low on stock, which is handled very nicely by the base class Reorder function.
We shouldn't pass by this function without noting one more point. The only reason that we can access m_InStock and the other member variables of the StockItem base class part of our object is that those member variables are protected rather than private. If they were private, we wouldn't be able to access them in our DatedStockItem functions, even though every DatedStockItem object would still have such member variables.
Susan didn't care for that last statement, but I think I talked her into accepting it.
Susan: I can't picture the statement "even though every DatedStockItem object would still have such member variables".
Steve: Well, every DatedStockItem has a StockItem base class part and that base class part contributes its member variables to the DatedStockItem. Even if we can't access them because they're private, they're still there.
Now we have a good solution to the creation of stock items with dates. Unfortunately, it's not possible to have a Vec of dissimilar types - that is, our current solution can't handle a combination of StockItem and DatedStockItem objects. On the other hand, it is possible to have a Vec of pointers that can refer to either StockItem or DatedStockItem objects, by making use of the characteristic of C++ that a pointer declared to point to a base class type can point to a derived class object.
However, using a Vec of StockItem*s to point to a mixture of StockItem and DatedStockItem objects won't give us the results we want with the current definitions of these classes. To be precise, if we call Reorder through a StockItem*, the wrong version of Reorder will be called for DatedStockItem objects. To help explain why this is so, I've drawn a number of diagrams that show how C++ determines which function is called for a given type of pointer.
Before we get to the first diagram, there's one new construct that I should explain: the use of operator new for an object of a class type. The first example of this usage is the statement SIPtr = new StockItem("beans",40,110);. In this statement, we're creating a StockItem via the expression new StockItem("beans",40,110)25 and then assigning the address returned by new to the variable SIPtr (whose name is supposed to represent "StockItem pointer"). It should be fairly obvious why we have to use operator new here: to allocate memory for the newly constructed object, just as we did when we used operator new to allocate memory for an array of chars when creating an object of our string class.26 The only difference from that usage is that when we use operator new with a class object, we have to choose a constructor to create the object. Here we're specifying the arguments for the name, price, and number in stock. Since the "normal" constructor will accept such types of arguments, it's the one that will be called. On the other hand, if we hadn't supplied any arguments - for example, by writing SIPtr = new StockItem; - the default constructor would be used to create the new StockItem object.
Now that I've explained this use of new, let's look at Figure 9.36, which shows how a normal function call works when Reorder is called for a StockItem object through a StockItem pointer.
Step 1 calls StockItem::Reorder via the StockItem* variable named SIPtr. When StockItem::Reorder finishes execution, it returns to the main program (step 2); since there isn't anything else to do in the main program, the program ends at that point.
Susan had a question about the syntax of the line SIPtr>Reorder(cout);.
Susan: What does that -> thing do again? I forget.
Steve: It separates a pointer to an object (on its left) from a member variable or function (on its right). In this case, it separates the pointer SiPtr from the function Reorder, so that line says that we want to call the function Reorder for whatever object SIPtr is pointing to. In other words, it does for pointers exactly what "." does for objects.
FIGURE 9.36.Calling Reorder through a StockItem pointer, part 1
So far, so good. Now let's see what happens when we call Reorder for a DatedStockItem object through a DatedStockItem pointer (Figure 9.37).
In Figure 9.37, step 1 calls DatedStockItem::Reorder via DSIPtr, a variable of type DatedStockItem*. When DatedStockItem::Reorder finishes execution, it returns to the main program (step 2); again; since there isn't anything else to do in the main program, the program ends at that point. That looks okay, too. But what happens if we call Reorder for a DatedStockItem object through a StockItem pointer, as in Figure 9.38?
Unfortunately, step 1 in Figure 9.38 is incorrect because the line SIPtr->Reorder(cout) calls StockItem::Reorder whereas we wanted it to call DatedStockItem::Reorder. This problem arises because when we call a normal member function through a pointer, the compiler uses the declared type of the pointer to decide which actual function will be called. In this case, we've declared SIPtr to be a pointer to a StockItem, so even though the actual data type of the object it points to is DatedStockItem, the compiler thinks it's a StockItem. Therefore, the line SIPtr->Reorder(cout) results in a call to StockItem::Reorder.
FIGURE 9.37.Calling Reorder through a DatedStockItem pointer
Before we see how to fix this problem, we should look at a test program that actually uses these versions of the Reorder functions with the simplified versions of our StockItem and DatedStockItem classes that we've been using for this discussion. Figure 9.39 shows the test program, Figure 9.40 shows the output of the test program, and Figure 9.41 shows the implementation of the classes.27 (You may very well want to print out the files that contain this interface and its implementation, as well as the test program. Those files are code\itema.h, code\itema.cpp, and code\nvirtual.cpp, respectively.).
FIGURE 9.38.Calling Reorder through a StockItem pointer, part 2
FIGURE 9.39. Function call example (code\nvirtual.cpp)
#include <iostream>
#include "itema.h"
using namespace std;
int main()
{
StockItem StockItemObject("soup",32,100);
StockItem* StockItemPointer;
DatedStockItem DatedStockItemObject("milk",
10,15,"19950110");
DatedStockItem* DatedStockItemPointer;
StockItemObject.Reorder(cout);
cout << endl;
DatedStockItemObject.Reorder(cout);
cout << endl;
StockItemPointer = new StockItem("beans",40,110);
StockItemPointer->Reorder(cout);
cout << endl;
DatedStockItemPointer = new DatedStockItem("ham",
22,30,"19970110");
DatedStockItemPointer->Reorder(cout);
cout << endl;
StockItemPointer = new DatedStockItem("steak",
90,95,"19960110");
StockItemPointer->Reorder(cout);
cout << endl;
}
FIGURE 9.40. Function call example output (code\nvirtual.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
StockItem::Reorder says:
Reorder 5 units of steak
FIGURE 9.41. Simplified implementation for StockItem and DatedStockItem classes (code\itema.cpp)
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include "itema.h"
#include <dos.h>
using namespace std;
StockItem::StockItem(string Name, short InStock,
short MinimumStock)
: m_InStock(InStock), m_Name(Name),
m_MinimumStock(MinimumStock)
{
}
void StockItem::Reorder(ostream& os)
{
short ActualReorderQuantity;
if (m_InStock < m_MinimumStock)
{
ActualReorderQuantity = m_MinimumStock - m_InStock;
os << "StockItem::Reorder says:" << endl;
os << "Reorder " << ActualReorderQuantity << " units of ";
os << m_Name << 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 >> TodaysDate;
return TodaysDate;
}
void DatedStockItem::Reorder(ostream& os)
{
if (m_Expires < Today())
{
os << "DatedStockItem::Reorder says:" << endl;
short ReturnQuantity = m_InStock;
m_InStock = 0;
os << "Return " << ReturnQuantity << " units of ";
os << m_Name << endl;
}
StockItem::Reorder(os);
}
DatedStockItem::DatedStockItem(string Name, short InStock,
short MinimumStock, string Expires)
: StockItem(Name, InStock,MinimumStock),
m_Expires(Expires)
{
}
There shouldn't be anything too surprising in this program. When we call the Reorder function for an object, we get the function for that type of object, and when we call the Reorder function through a pointer to an object, we get the function for that type of pointer. However, what we really want is to have the DatedStockItem version of Reorder called if the object in question is a DatedStockItem even if the pointer is of type StockItem*. We'll see how to solve that problem in the next chapter.
9.10. Review
We started the chapter with a simple inventory control example that used a StockItem class and an Inventory class, including a function called ReorderItem that can be called for an Inventory object to produce a reordering report. This function calls a Reorder function for each StockItem in its StockItem Vec to calculate the quantity of that StockItem to be ordered based on the desired and current stock.
Then we built on that StockItem class by adding an expiration date. Rather than copying all of the old code and class definitions, we made use of a concept that is essential to the full use of C++ for object-oriented programming - inheritance. Inheritance is a method of constructing one class (the derived class) by specifying how it differs from another class (the base class) rather than writing it from scratch. We used inheritance to create a new DatedStockItem class that had all of the capabilities of the StockItem class, adding the ability to handle items with expiration dates.
In the process, we wrote a new Reorder function with the same signature as that of the base class function of the same name. This is called overriding the base class function. When the function with that signature is called via an object of the base class, the base class function will be called. On the other hand, when the function with that signature is called via an object of the derived class, the derived class function will be called. This allows a derived class to supply the same functionality as that of a base class but to implement it in a different way.
A derived class object can do anything that a base class object can do because a derived class object actually contains an object of the base class, called the base class part of the derived class object. This base class part is very similar to a member variable in the derived class, but it is not the same for two reasons:
1. A member variable always has a name, whereas the base class part does not.
2. The base class definition can give derived class member functions privileged access to some member variables and functions of the base class part of an object of the derived class, by marking those member variables and functions with the access specifier keyword protected.
In the process of writing the new Reorder function for the DatedStockItem class, we saw how we could store a date as a string that allowed comparison of two dates to see which was later. This required us to create a formatted string representing the date as YYYYMMDD - that is, a four-digit year number, a two-digit month number, and a two-digit day number. Getting the current date wasn't too hard; we used the date variable type along with its associated getdate function to retrieve the year, month, and day of the current date. However, once we had this information, we still had to combine the parts of the date into a formatted string. One way to do this is to use the stringstream data type, which is a stream that exists only in memory as a formatting aid. This topic led to a discussion of how we can specify the formatting of data rather than accept the default formatting, as we did previously; it also led to the related discussion of using the stringstream class to generate formatted output.
After delving into the general topic of streams in more detail, we returned to the more specific issue of stringstreams, which allowed us to solve our formatting problem by combining the << operator with the manipulators setw and setfill to control the width and fill characters of the data we wrote to the stringstream.
Once we had used << to write the data to the stringstream in the required YYYYMMDD format, we used >> to read it back into a string for comparison with the expiration date stored in a DatedStockItem object (see Figures 9.28 and 9.29).
After discussing the formatting of the date string, we continued by examining the default constructor of the DatedStockItem class. While this is an extremely short function, having only one member initialization expression and no code in the constructor proper, there is more to it than meets the eye. The default constructor deals only with the newly added member variable, m_Expires, but behind the scenes the base class part of the DatedStockItem object is being initialized by the default constructor of the base class - StockItem::StockItem(). The rule is that a base class constructor will always be called for the base class part of a derived class object. If we don't specify which base class constructor we want, the default constructor for the base class will be used. To select the constructor for the base class part, we can use a construct known as a base class initializer in the member initializer list in the derived class constructor. In our "normal" constructor for DatedStockItem, we used this construct to call the corresponding constructor for the base class (see Figures 9.33 and 9.34).
Then we looked at the Reorder function for the DatedStockItem class, which includes code to request the return of any items that are past their expiration date and calls the base class Reorder function to handle the rest of the job.
At that point, we had a working DatedStockItem class, but we still couldn't mix StockItem and DatedStockItem objects in the same Vec. However, it was possible to create a Vec of pointers to StockItems. Once we did that, we could make any of those pointers point to a DatedStockItem, employing the C++ feature that a base class pointer can also point to an object of a derived class. After seeing how to use operator new to allocate StockItem and DatedStockItem variables, we discovered that just using a base class pointer doesn't do what we wanted. As these classes are currently defined, the version of the Reorder function called through a StockItem pointer is always the base class version, rather than the correct version for the actual type of the object the pointer refers to. We'll see how to fix that problem in the next chapter.
9.11. Exercises
1. Suppose that the store using our inventory control program adds a new pharmacy department. Most of their items are nonprescription medications that can be handled with the DatedStockItem class we already created, but their prescription drug items need to be handled more carefully. This means that the member function DeductSaleFromInventory has to ask for a password before allowing the sale to take place. Create a new DrugStockItem class that enforces this new rule without using inheritance.
2. The store also needs some way to keep track of its employees' hours so it can calculate their pay. We'll assume that the employees are paid their gross wages, ignoring taxes. These wages are calculated as follows:
a. Managers are paid a flat amount per week, calculated as their hourly rate multiplied by 40 hours.
b. Hourly employees are paid a certain amount per hour no matter how many hours they work (i.e., overtime is not paid at a higher rate).
Write an Employee class that allows the creation of Employee objects with a specified hourly wage level and either "manager" or "hourly" salary rules. The pay for each object is to be calculated via a CalculatePay member function that uses the "manager" or "hourly" category specified when the object was created. Use the short data type to keep track of the pay rate and the total pay for each employee for the week, assuming that only a whole number of hours can be specified and that wage rates are always expressed in dollars rather than dollars and cents.28
3. Rewrite the DrugStockItem class from Exercise 1 using inheritance from the DatedStockItem class.
4. Rewrite the Employee class from Exercise 2 as two classes: the base Exempt class and an Hourly class derived from the base class. The CalculatePay member function for each of these classes should use the appropriate method of calculating the pay for each class. In particular, this member function doesn't need an argument specifying the number of hours worked for the Exempt class, while the corresponding member function in the Hourly class does need such an argument.
5. Rewrite the Employee class that you wrote in Exercise 2 as two classes: the base Exempt class and an Hourly class derived from the base class. To maintain the same interface for these two classes, the CalculatePay member function in both classes should have an argument specifying the number of hours worked. The implementation of the Exempt class will ignore this argument, while the Hourly implementation will use it.
6. Write an essay comparing the advantages and disadvantages of the two approaches to inheritance in the previous two exercises.29
7. Reimplement the DatedStockItem class to use a long variable of the form YYYYMMDD, rather than a string, to store the date.30 Is this a better approach than using a string? Why or why not?
8. Rewrite the operator << functions for DatedStockItem and StockItem so that the former function uses the latter to display the common data items for the two classes, rather than display all the data items itself.
9.12. Conclusion
In this chapter, we defined a StockItem class and then extended the functionality provided in that class by deriving a new class, DatedStockItem, based on StockItem.
However, we have not yet seen how objects of these two classes can be used interchangeably. Although we can use base class pointers to point to objects of both base and derived types, we can't yet arrange for the correct function to be called based on the actual type of the object to which the pointer refers. In Chapter 10, we will see how to overcome this barrier.
1 As elsewhere in this book, when I speak of the "user" of a class, I mean the application programmer who is using objects of the class to perform work in his or her program, not the "end user" who is using the finished program.
2 See "The stream classes" on page 621.
3 Note that the declarations of LoadInventory, StoreInventory, and ReorderItems refer to istreams and ostreams, not the file stream types. As I mentioned previously, this is legal because of the relationships among these types. I'll explain exactly why on page 613.
4 Of course, thinking that your program can't possibly last long enough that you need to worry about running off the end of its legal date range is what led to a lot of frantic maintenance work as we approached the year 2000.
5 In case you're wondering why I allocated 4 digits for the year, it was to ensure that the program will work both before and after a change of the century part of the date (e.g., from 1999 to 2000). Unfortunately, not all programmers have been so considerate. Many programs use a 2-digit number to represent the year portion of a date in the form YYMMDD and as a result, behaved oddly during the so-called "Y2K transition".
6 While maintenance has long been a neglected area of computer science, and the programming industry for that matter, that has changed for the better in recent years.
7 I'm simplifying by leaving out the internal structure of a string, which affects the actual layout of the object; this detail isn't relevant here.
8 Actually, this is not strictly true. We can add functions to a derived class without affecting how it appears to users, so long as the functions that we add are either private or protected, so that they don't change the public interface of the class. We'll see some examples of this later.
9 When we write a derived class constructor, the base class default constructor is called to initialize the base class part of the object if we don't say which base class constructor we want; however, we can tell the compiler explicitly to call a different base class constructor.
10 It may seem that this automatic calling of base class functions for the constructor, destructor, and assignment operator is a type of inheritance. However, it really isn't because those functions are applied to the base class part of the derived class object, not to the derived class object itself. In the same way, you can refer to base class functions explicitly by qualifying the function name with the class name. This is also not a case of inheritance because these functions are applied only to the base class part.
11 Here's a little more detail on how the compiler-generated assignment operator works. It assigns all of the members of the source variable to the destination variable (using their assignment operators if they are user-defined types) and uses the assignment operator of the base class to copy the base class part.
12 Of course, because m_Expires is a member of DatedStockItem, all DatedStockItem member functions can access it freely regardless of its access specifier.
13 The reason that I am not listing either the header file or the implementation file for invent21 (the new version of the inventory class) is that they are essentially identical to the previous versions except that they use DatedStockItem rather than StockItem to keep track of the inventory items.
14 After looking at the interface file, you may wonder why I have two protected access specifiers in the DatedStockItem class declaration. The reason is that I like to explicitly state the access specifiers for functions and for data separately to clarify what I'm doing. This duplication doesn't mean anything to the compiler, but it makes my intention clearer to the next programmer.
15 However, programs using this header file will work in a "DOS box" under any version of Windows` that I have tried it with, so you don't have to be running DOS itself to be able to use them.
16 By the way, this is the reason it's all right to provide an ifstream variable where an istream is expected, as I told Susan in the discussion on page 352. Because ifstream is publicly derived from istream, an ifstream "isAn" istream. This means that you can supply an ifstream wherever an istream is specified as an argument.
17 private inheritance is most useful when we are using inheritance to extend the facilities provided by an existing class in "inheritance for extension".
18 From what I can gather from discussions on Usenet about this topic, the reason that the compiler won't warn about slicing is that the design of C++ requires this to be legal, not because there is any particular value to this "feature" of the language.
19 Actually, the best solution would have been for getdate to return a date variable rather than changing its argument, either directly or indirectly. That would work even in C. I guess the designers of getdate didn't think of that possibility.
20 Please note that the type of the put pointer is irrelevant to us, as we cannot ever access it directly. However, you won't go far wrong if you think of it as the address of the next available byte in the buffer.
21 This is covered in the discussion of fail() starting on page 354.
22 Actually, our comparison functions would work correctly even if we left the fill character at its default value of "space", but the date strings would contain spaces instead of zeroes for day and month numbers less than 10, and they would look silly that way!
23 By the way, it is not a good idea to change the behavior of streams that you didn't create and might be used by another function after you get through with them, e.g., by changing the fill character setting for cout, as that can cause odd behavior in the rest of the program. However, this doesn't apply to streams that exist only inside a particular function, as in this case.
24 There are other functions whose implementation in DatedStockItem is different from the versions in StockItem, but we'll wait until later to discuss them. These are the input and output functions FormattedDisplay, operator >>, and operator <<.
25 The examples in this section use simplified versions of the StockItem and DatedStockItem classes to make the diagrams smaller; the principles are the same as with the full versions of these classes.
26 This was covered in Chapter 7.
27 The interface for these simplified StockItem and DatedStockItem classes was shown in Figure 9.7.
28 We'll see how we could handle more realistic values for the pay and number of hours using another numeric data type in a later chapter.
29 If you'll e-mail this essay to me, I might put it on my WWW page!
30 A long is just like a short, except that it can hold larger numbers. See the Glossary for details on this type.