10.8. Implementing Safe Polymorphism
Now that we have an overview of the structure of the classes we're designing, Figure 10.23 shows the interfaces for the worker classes UndatedStockItem and DatedStockItem.
FIGURE 10.23. Safe polymorphism: The UndatedStockItem and DatedStockItem interfaces for the polymorphic version of StockItem (code\itempi.h)
FIGURE 10.24. Safe polymorphism: The implementation of the UndatedStockItem and DatedStockItem classes (code\itemp.cpp)
Let's start our examination of this new StockItem class by looking at the implementation of operator << in Figure 10.25.
FIGURE 10.25. Safe polymorphism: The implementation of operator << for a polymorphic StockItem (from code\itemp.cpp)
At first glance, this isn't particularly complicated. It just calls a function named Write via the StockItem* member variable m_Worker and returns the result from Write to its caller. But what does that m_Worker pointer point to?
This is the key to the implementation of polymorphic objects. The pointer m_Worker points to either a DatedStockItem or an UndatedStockItem depending on whether the object was created with or without an expiration date, respectively. Since the type of object that m_Worker points to is determined during program execution rather than when the program is compiled, the actual version of the Write function called by operator << varies accordingly, as it does with any type of polymorphism. The difference between this version of StockItem and the one described earlier in this chapter is that the pointer is used only in the implementation of StockItem rather than being accessible to the user of the class. This allows us to prevent the plague of memory allocation errors associated with pointer manipulation in the application program.
- Susan: What does the -> mean in that line?
- Steve: It means to call the function on the right of the -> for the object pointed to by the pointer on the left of the ->. It's exactly like the . operator, except that operator has an object on its left instead of a pointer.
Before we get into the details of creating an object of the type defined by this new version of StockItem, we'll look at a couple of diagrams of the StockItem variables from the example program in Figure 10.21 to see exactly how this "internal polymorphism" works. First, Figure 10.26 shows a possible layout of the StockItem object, x, which is the argument to operator << in the statement cout << x;.
Let's trace the execution of the statement return Item.m_Worker>Write(os); from operator << (Figure 10.25 on page 710) when it is executed to display the value of the StockItem x (as a result of the statement cout << x; in the example program in Figure 10.21 on page 696). In step 1, the pointer m_Worker is followed to location 12321000, the address of the beginning of the UndatedStockItem worker object that will handle the operations of the StockItem manager class object. This location contains the address of the vtable for the worker object. In this case, the worker object is an UndatedStockItem object, so the vtable is the one for the UndatedStockItem class.
In our diagram that vtable is at location 12380000, so in step 2 we follow the vtable pointer to find the address of the Write function. The diagram makes the assumption that the Write function is the second virtual function defined in the StockItem class, so in step 3 we fetch the contents of the second entry in the vtable, which is 12390900. That is the address of the Write function that will be executed here.
Figure 10.27 shows a possible layout of the StockItem object y that is the argument to operator << in the statement cout << y;.
- Susan: What in Figure 10.27 is the StockItem? I can't tell exactly what it is supposed to be, just the vtable address, m_Worker, and m_Count? That's it, huh?
- Steve: Yes.
- Susan: What are all those member functions in step 3 supposed to be? I don't know where they're coming from.
- Steve: They're from the class interface for the new version of StockItem, which is listed in Figure 10.22.
- Susan: Why is there an UndatedStockItem listed along the side of the DatedStockItem thingy?
- Steve: Since DatedStockItem is derived from UndatedStockItem, every DatedStockItem has an UndatedStockItem in it, just as every UndatedStockItem has a StockItem in it because UndatedStockItem is derived from StockItem.
- Susan: Where is the UndatedStockItem now?
- Steve: The only UndatedStockItem in Figure 10.27 is the base class part of the DatedStockItem worker object.
- Susan: I still can't see how these objects look.
- Steve: I think we need a diagram here. Take a look at Figure 10.28 and let me know if that helps.
- Susan: Yes, it does.
Now that we've cleared that up, let's trace how the line return Item.m_Worker>Write(os); is executed to display the value of the StockItem y (as a result of the statement cout << y; in the example program in Figure 10.21). In step 1, the pointer m_Worker is followed to location 22321000, the address of the worker object that will handle the operations of the StockItem manager class object. This location contains the address of the vtable for the worker object. In this case, the worker object is a DatedStockItem object, so the vtable is the one for the DatedStockItem class. In our diagram, that vtable is at location 22380000, so in step 2 we follow the vtable pointer to find the address of the Write function. As before, the diagram makes the assumption that the Write function is the second virtual function defined in the StockItem class, so in step 3 we fetch the contents of the second entry in the vtable, which is 22390900. That is the address of the Write function that will be executed in this case.
|
www.steveheller.com steve@steveheller.com |