Avoiding an Infinite Regress During Construction

We can't do that because the default constructor for StockItem calls the default constructor for UndatedStockItem; that is, the function that we're examining right now. Therefore, if we allow the StockItem default constructor to initialize the StockItem part of an UndatedStockItem, that default constructor will call our UndatedStockItem default constructor again, which will call the StockItem default constructor again, and the program will eventually use up all the stack space and die.

To avoid this problem, we have to make a special constructor for StockItem that doesn't create an UndatedStockItem object and therefore avoids an indefinitely long chain of constructor calls. As no one outside the implementation of the StockItem polymorphic object knows anything about classes in this idiom other than StockItem, they don't need to call this constructor. As a result, we can make it protected.

Susan didn't see why we need a special constructor in this case, so I explained it to her some more:

Susan: So, how many default constructors are you going to need for StockItem? How do you know when or which one is going to be used? This is confusing.

Steve: There is only one default constructor for each class. In this case, StockItem has one, DatedStockItem has one, and UndatedStockItem has one. The question is how to prevent the StockItem default constructor from being called from the UndatedStockItem one, which would be a big booboo, since the UndatedStockItem default constructor was called from the StockItem default constructor. This would be like having two mirrors facing one another, where you see endless reflections going off into the distance.

Okay, so how do we declare a special constructor for this purpose? As was shown in Figure 10.22, all we have to do is to put the line StockItem(int); in a protected section of the class definition. The implementation of this function is shown in Figure 10.32.

Figure 10.32. Safe polymorphism: Implementing a special protected constructor for StockItem (from codeitemp.cpp)
StockItem::StockItem(int)
: m_Worker(0)
{
}

How can a function that doesn't have any code inside its { } be complicated? In fact, this apparently simple function raises three questions. First, why do we need any entries in the argument list when the function doesn't use any arguments? Second, why does the list contain just a type and no argument name instead of a name and type for each argument, as we had in the past? And third, why are we initializing m_Worker to the value 0? We'll examine the first two of these questions now and put off the third until we discuss the destructor for the StockItem class.

The answers to the first two questions are related. The reason we don't need to specify a name for the argument is that we aren't going to use the argument in the function. The only reason to specify an argument list here is to make use of the function overloading mechanism, which allows the compiler to distinguish between functions with the same name but different argument types. In this case, even though all the constructors for the StockItem class have the same name — StockItem::StockItem — the compiler can tell them apart so long as they have different argument types. Therefore, we're supplying an argument we don't need to allow the compiler to pick this function when we want to use it. Here, when we call the function from the worker object's constructor, we supply the value 1, which will be ignored in the function itself but will tell the compiler that we want to call this constructor rather than any of the other constructors.

Susan wanted to know exactly where the 1 was going to come from and how I decided to use that value in the first place:

Susan: Where are you supplying the 1 value?

Steve: In the base class initializer in the default constructor for UndatedStockItem. This is needed to prevent the infinite regress mentioned just above.

Susan: But why 1? Why not any other number, like 0?

Steve: Actually, the value 1 is fairly arbitrary; any number would work... except 0. In general, it's a good idea to avoid using 0 where you just need a number but don't care which one, because 0 is a “magic number” in C++; it's a legal value for any type of built-in variable as well as any type of pointer. This “multiple identity” of 0 can be a bountiful source of confusion and error in C++, which it's best to avoid whenever possible.

So that explains why we need an argument that we're not using. But why leave the name of the argument out of the function header?

There's nothing to stop us from giving the argument a name even though we aren't going to use it in the constructor. It's better not to do this, however, to avoid confusing both the compiler and the next programmer who looks at this function. The compiler may give us a warning message if we don't use an argument we've declared, while the next programmer to look at this function may think we forgot to use that argument. We can solve both of these problems by not giving it a name, which makes it clear that we weren't planning to use it in the first place.

So now we've followed the chain of events down to the initialization of the base class part of the UndatedStockItem object that was created as the worker object inside the default constructor for StockItem. The rest of Figure 10.31 is pretty simple. It merely initializes the data for the UndatedStockItem class itself. When that's done, we're ready to execute the lone statement inside the {} in Figure 10.29 on page 712, which is m_Worker->m_Count = 1;. Clearly, this sets the value of m_Count in the newly created UndatedStockItem object to 1, but what might not be as clear is why we need to do this.

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

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