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 (codestream2.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]

[21] This is covered in the discussion of fail() starting on page 350.

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 (codecoutdef1.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 (codecoutdef1.out)
date1: 19961228, date2: 1996728
date1 is less than date2

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 (codecoutdef2.out)
date1: 19961228, date2: 19960728
date1 is greater than date2

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]

[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!

Figure 9.29. Controlled formatting example (codecoutdef2.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.

[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.

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]

[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 <<.

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 codeitem21.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()
: StockItem(),
  m_Expires()
{
}

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 codeitem21.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.

Figure 9.34. Constructing a DatedStockItem object


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.

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 codeitem21.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.

[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.

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.

Figure 9.36. Calling Reorder through a StockItem pointer, part 1


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.

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

Figure 9.37. Calling Reorder through a DatedStockItem pointer


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?

Figure 9.38. Calling Reorder through a StockItem pointer, part 2


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.

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 codeitema.h, codeitema.cpp, and code virtual.cpp, respectively.).

[27] The interface for these simplified StockItem and DatedStockItem classes was shown in Figure 9.7.

Figure 9.39. Function call example (code virtual.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 virtual.out)
StockItem::Reorder says:
Reorder 68 units of soup

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

StockItem::Reorder says:
Reorder 70 units of beans

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

StockItem::Reorder says:
Reorder 5 units of steak

Figure 9.41. Simplified implementation for StockItem and DatedStockItem classes (codeitema.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.

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

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