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

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

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.

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

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