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.

Figure 9.10. A derived class object with its base class part

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]

[12] Of course, because m_Expires is a member of DatedStockItem, all DatedStockItem member functions can access it freely regardless of its access specifier.

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.

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 (codeitmtst21.cpp)
#include <iostream>
#include <fstream>
#include "Vec.h"
#include "item21.h"
#include "invent21.h"
using namespace std;

int main()
{
  ifstream ShopInfo("shop21.in");
  ofstream ReorderInfo("shop21.reo");

  Inventory MyInventory;
  MyInventory.LoadInventory(ShopInfo);

  MyInventory.ReorderItems(ReorderInfo);

  return 0;
}

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]

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

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 codeitem21.h and codeitem21.cpp, respectively.[14]

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

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

[15] However, programs using this header file will work in a “DOS box” under any version of Windows'54'4d that I have tried it with, so you don't have to be running DOS itself to be able to use them.

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]

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

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.

[17] private inheritance is most useful when we are using inheritance to extend the facilities provided by an existing class in “inheritance for extension”.

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]

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

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.

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

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