Chapter 14. Reusing Code in C++

One of the main goals of C++ is to facilitate the reuse of code. Public inheritance is one mechanism for achieving this goal, but it’s not the only one. This chapter investigates other choices. One technique is to use class members that are themselves objects of another class. This is referred to as containment or composition or layering. Another option is to use private or protected inheritance. Containment, private inheritance, and protected inheritance are typically used to implement has-a relationships—that is, relationships for which the new class has an object of another class. For example, a HomeTheater class might have a DvdPlayer object. Multiple inheritance lets you create classes that inherit from two or more base classes, combining their functionality.

Chapter 10, “Objects and Classes,” introduces function templates. In this chapter we’ll look at class templates, which provide another way of reusing code. A class template lets you define a class in generic terms. Then you can use the template to create specific classes defined for specific types. For example, you could define a general stack template and then use the template to create one class that represents a stack of int values and another class that represents a stack of double values. You could even generate a class that represents a stack of stacks.

Classes with Object Members

Let’s begin with classes that include class objects as members. Some classes, such as the string class or the standard C++ class templates discussed in Chapter 16, “The string Class and the Standard Template Library,” offer convenient ways of representing components of a more extensive class. Let’s look at a particular example now.

What is a student? Someone enrolled in a school? Someone engaged in thoughtful investigation? A refugee from the harsh exigencies of the real world? Someone with an identifying name and a set of quiz scores? Clearly, the last definition is a totally inadequate characterization of a person, but it is well suited for a simple computer representation. So let’s develop a Student class based on that definition.

Simplifying a student to a name and a set of quiz scores suggests using a class with two members: one to represent the name and one to represent the scores. For the name, you could use a character array, but that puts a size limitation on the name. Or you could use a char pointer and dynamic memory allocation. However, as Chapter 12, “Classes and Dynamic Memory Allocation,” illustrates, that requires a lot of supporting code. Better yet, you could use an object of a class for which someone has already done all the work. For example, you could use an object of the String class (see Chapter 12) or of the standard C++ string class. The simpler choice is the string class because the C++ library already provides all the implementation code. (To use the String class, you’d have to make the string1.cpp implementation file part of your project.)

Representing the quiz scores presents similar choices. You could use a fixed-size array, which places a size limitation. You could use dynamic memory allocation and provide a large body of supporting code. You could use your own design of a class, using dynamic memory allocation to represent an array. You could look for a standard C++ library class that is capable of representing the data.

The problem with the third choice is that you haven’t developed such a class. A simple version wouldn’t be that difficult because an array of double shares many similarities with an array of char, so you could base the design of an array-of-double class on the String class design. And, in fact, that is what earlier editions of this book do.

But, of course, it is even easier if the library already provides a suitable class, and it does: the valarray class.

The valarray Class: A Quick Look

The valarray class is supported by the valarray header file. As its name suggests, the class is targeted to deal with numeric values (or with classes with similar properties), so it supports operations such as summing the contents and finding the largest and smallest values in an array. So that it can handle different data types, valarray is defined as a template class. Later, this chapter goes into how to define template classes, but all you need to know now is how to use one.

The template aspect means that you have to specify a specific type when declaring an object. To do so when declaring an object, you follow the identifier valarray with angle brackets that contain the desired type:

valarray<int> q_values;     // an array of int
valarray<double> weights;   // an array of double

This is the only new syntax you need to learn, and it’s pretty easy.

The class aspect means that to use valarray objects, you need to know something about class constructors and other class methods. Here are several examples that use some of the constructors:

image

As you can see, you can create an empty array of zero size, an empty array of a given size, an array with all elements initialized to the same value, and an array initialized using the values from an ordinary array.

Next, here are a few of the methods:

operator[]() provides access to individual elements.

size() returns the number of elements.

sum() returns the sum of the elements.

max() returns the largest element.

min() returns the smallest element.

There are many more methods, some of which are presented in Chapter 16, but you’ve already seen more than enough to proceed with this example.

The Student Class Design

At this point, the design plan for the Student class is to use a string object to represent the name and a valarray<double> object to represent the quiz scores. How should this be done? You might be tempted to publicly derive a Student class from these two classes. That would be an example of multiple public inheritance, which C++ allows, but it would be inappropriate here. The reason is that the relationship of a student to these classes doesn’t fit the is-a model. A student is not a name. A student is not an array of quiz scores. What you have here is a has-a relationship. A student has a name, and a student has an array of quiz scores. The usual C++ technique for modeling has-a relationships is to use composition or containment—that is, to create a class composed of, or containing, members that are objects of another class. For example, you can begin a Student class declaration like this:

image

As usual, the class makes the data members private. This implies that the Student class member functions can use the public interfaces of the string and valarray<double> classes to access and modify the name and scores objects, but the outside world cannot do so. The only access the outside world will have to name and scores is through the public interface defined for the Student class (see Figure 14.1). A common way of describing this is to say that the Student class acquires the implementation of its member objects but doesn’t inherit the interface. For example, a Student object uses the string implementation rather than a char * name or a char name[26] implementation for holding the name. But a Student object does not innately have the ability to use the string operator+=() function for appending

Figure 14.1. Objects within objects: containment.

image

The fact that a class object doesn’t automatically acquire the interface of a contained object is a good thing for a has-a relationship. For example, string overloads the + operator to allow concatenating two strings, but, conceptually, it doesn’t make sense to concatenate two Student objects. That’s one reason not to use public inheritance in this case. On the other hand, parts of the interface for the contained class may make sense for the new class. For example, you might want to use the operator<() method from the string interface to sort Student objects by name. You can do so by defining a Student::operator<() member function that, internally, uses the string::operator<() function. Let’s move on to some details.

The Student Class Example

At this point you need to provide the Student class declaration. It should, of course, include constructors and at least a few functions to provide an interface for the Student class. Listing 14.1 does this, defining all the constructors inline. It also supplies some friends for input and output.

Listing 14.1. studentc.h

image

image

In order to simplify notation, the Student class contains this typedef:

typedef std::valarray<double> ArrayDb;

This enables the remaining code to use the more convenient notation ArrayDb instead of std::valarray<double>. Thus, methods and friends can refer to the ArrayDb type. Placing this typedef in the private portion of the class definition means that it can be used internally in the Student implementation but not by outside users of the Student class.

Note the use of the keyword explicit:

explicit Student(int n) : name("Nully"), scores(n) {}

Recall that a constructor that can be called with one argument serves as an implicit conversion function from the argument type to the class type. In this example, the first argument represents the number of elements in an array rather than a value for the array, so having the constructor serve as an int-to-Student conversion function does not make sense. Using explicit turns off implicit conversions. If this keyword were omitted, code like the following would be possible:

Student doh("Homer", 10);  // store "Homer", create array of 10 elements
doh = 5;   // reset name to "Nully", reset to empty array of 5 elments

Here, the inattentive programmer typed doh instead of doh[0]. If the constructor omitted explicit, 5 would be converted to a temporary Student object, using the constructor call Student(5), with the value of "Nully" being used to set the name member. Then assignment would replace the original doh with the temporary object. With explicit in place, the compiler will catch the assignment operator as an error.

Real-World Note: C++ and Constraints

image

C++ is full of features that allow programmers to constrain programmatic constructs to certain limits—explicit to remove the implicit conversion of single-argument constructors, const to constrain the use of methods to modify data, and more. The underlying motive is simply this: Compile-time errors are better than runtime errors.

Initializing Contained Objects

Note that constructors all use the by-now-familiar member initializer list syntax to initialize the name and scores member objects. In some cases earlier in this book, such as the following, the constructors use it to initialize members that are built-in types:

Queue::Queue(int qs) : qsize(qs)  {...} // initialize qsize to qs

This code uses the name of the data member (qsize) in a member initializer list. Also, constructors from previous examples, such as the following, use a member initializer list to initialize the base-class portion of a derived object:

hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {...}

For inherited objects, constructors use the class name in the member initializer list to invoke a specific base class constructor. For member objects, constructors use the member name. For example, look at the last constructor in Listing 14.1:

Student(const char * str, const double * pd, int n)
       : name(str), scores(pd, n) {}

Because it initializes member objects, not inherited objects, this constructor uses the member names, not the class names, in the initialization list. Each item in this initialization list invokes the matching constructor. That is, name(str) invokes the string(const char *) constructor, and scores(pd, n) invokes the ArrayDb(const double *, int) constructor, which, because of the typedef, really is the valarray<double>(const double *, int) constructor.

What happens if you don’t use the initialization list syntax? As with inherited components, C++ requires that all member objects be constructed before the rest of an object is constructed. So if you omit the initialization list, C++ uses the default constructors defined for the member objects’ classes.

Using an Interface for a Contained Object

The interface for a contained object isn’t public, but it can be used within the class methods. For example, here is how you can define a function that returns the average of a student’s scores:

double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum()/scores.size();
    else
        return 0;
}

This defines a method that can be invoked by a Student object. Internally, it uses the valarray size() and sum() methods. That’s because scores is a valarray object, so it can invoke the member functions of the valarray class. In short, the Student object invokes a Student method, and the Student method uses the contained valarray object to invoke valarray methods.

Similarly, you can define a friend function that uses the string version of the << operator:

image

Because stu.name is a string object, it invokes the operator<<(ostream &, const string &) function, which is provided as part of the string class package. Note that the operator<<(ostream & os, const Student & stu) function has to be a friend to the Student class so that it can access the name member. (Alternatively, the function could use the public Name() method instead of the private name data member.)

Similarly, the function could use the valarray implementation of << for output; unfortunately, there is none. Therefore, the class defines a private helper method to handle this task:

image

Using a helper like this gathers the messy details together in one place and makes the coding of the friend function neater:

image

The helper function could also act as a building block for other user-level output functions, should you choose to provide them.

Listing 14.2 shows the class methods file for the Student class. It includes methods that allow you to use the [] operator to access individual scores in a Student object.

Listing 14.2. studentc.cpp

image

image

Aside from the private helper method, Listing 14.2 doesn’t require much new code. Using containment allows you to take advantage of the code you or someone else has already written.

Using the New Student Class

Let’s put together a small program to test the new Student class. To keep things simple, it should use an array of just three Student objects, each holding five quiz scores. And it should use an unsophisticated input cycle that doesn’t verify input and that doesn’t let you cut the input process short. Listing 14.3 presents the test program. Be sure to compile it along with studentc.cpp.

Listing 14.3. use_stuc.cpp

image

image

Compatibility Note

image

If your system doesn’t correctly support the string friend getline() function, it won’t run this program correctly. You could modify the program to use the operator>>() from Listing 14.2 instead. Because this friend function reads just one word, you would change the prompt to ask for just the last name.

Here is a sample run of the program in Listings 14.1, 14.2, and 14.3:

image

Private Inheritance

C++ has a second means of implementing the has-a relationship: private inheritance. With private inheritance, public and protected members of the base class become private members of the derived class. This means the methods of the base class do not become part of the public interface of the derived object. They can be used, however, inside the member functions of the derived class.

Let’s look at the interface topic more closely. With public inheritance, the public methods of the base class become public methods of the derived class. In short, the derived class inherits the base-class interface. This is part of the is-a relationship. With private inheritance, the public methods of the base class become private methods of the derived class. In short, the derived class does not inherit the base-class interface. As you saw with contained objects, this lack of inheritance is part of the has-a relationship.

With private inheritance, a class does inherit the implementation. For example, if you base a Student class on a string class, the Student class winds up with an inherited string class component that can be used to store a string. Furthermore, the Student methods can use the string methods internally to access the string component.

Containment adds an object to a class as a named member object, whereas private inheritance adds an object to a class as an unnamed inherited object. This book uses the term subobject to denote an object added by inheritance or by containment.

Private inheritance, then, provides the same features as containment: Acquire the implementation, don’t acquire the interface. Therefore it, too, can be used to implement a has-a relationship. In fact, you can produce a Student class that uses private inheritance and has the same public interface as the containment version. Thus the differences between the two approaches affect the implementation, not the interface. Let’s see how you can use private inheritance to redesign the Student class.

The Student Class Example (New Version)

To get private inheritance, you use the keyword private instead of public when defining the class. (Actually, private is the default, so omitting an access qualifier also leads to private inheritance.) The Student class should inherit from two classes, so the declaration should list both:

class Student : private std::string, private std::valarray<double>
{
public:
    ...
};

Having more than one base class is called multiple inheritance (MI). In general, MI, particularly public MI, can lead to problems that have to be resolved with additional syntax rules. We’ll talk about such matters later in this chapter. But in this particular case, MI causes no problems.

Note that the new class doesn’t need private data. That’s because the two inherited base classes already provide all the needed data members. The containment version of this example provides two explicitly named objects as members. Private inheritance, however, provides two nameless subobjects as inherited members. This is the first of the main differences in the two approaches.

Initializing Base-Class Components

Having implicitly inherited components instead of member objects affects the coding of this example because you can no longer use name and scores to describe the objects. Instead, you have to go back to the techniques you used for public inheritance. For example, consider constructors. Containment uses this constructor:

Student(const char * str, const double * pd, int n)
   : name(str), scores(pd, n) {}      // use object names for containment

The new version should use the member initializer list syntax for inherited classes, which uses the class name instead of a member name to identify a constructor:

Student(const char * str, const double * pd, int n)
   : std::string(str), ArrayDb(pd, n) {}  // use class names for inheritance

Here, as in the preceding example, ArrayDb is a typedef for std::valarray<double>. Be sure to note that the member initializer list uses terms such as std::string(str) instead of name(str). This is the second main difference in the two approaches.

Listing 14.4 shows the new class declaration. The only changes are the omission of explicit object names and the use of class names instead of member names in the inline constructors.

Listing 14.4. studenti.h

image

Accessing Base-Class Methods

Private inheritance limits the use of base-class methods to within derived-class methods. Sometimes, however, you might like to make a base-class facility available publicly. For example, the Student class declaration suggests the ability to use an Average() function. As with containment, the technique for doing this is to use the valarray size() and sum() methods within a public Student::average() function (see Figure 14.2). Containment invoked the methods with an object:

double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum()/scores.size();
    else
        return 0;
}

Figure 14.2. Objects within objects: private inheritance.

image

Here, however, inheritance lets you use the class name and the scope-resolution operator to invoke base-class methods:

double Student::Average() const
{
    if (ArrayDb::size() > 0)
        return ArrayDb::sum()/ArrayDb::size();
    else
        return 0;
}

In short, the containment approach uses object names to invoke a method, whereas private inheritance uses the class name and the scope-resolution operator instead.

Accessing Base-Class Objects

The scope-resolution operator allows you access to a base-class method, but what if you need the base-class object itself? For example, the containment version of the Student class implements the Name() method by having the method return the name member string object. But with private inheritance, the string object has no name. How, then, can Student code access the inner string object?

The answer is to use a type cast. Because Student is derived from string, it’s possible to type cast a Student object to a string object; the result is an inherited string object. Recall that the this pointer points to the invoking object, so *this is the invoking object—in this case, a type Student object. To avoid invoking constructors to create new objects, you use the type cast to create a reference:

const string & Student::Name() const
{
    return (const string &) *this;
}

This code returns a reference to the inherited string object residing in the invoking Student object.

Accessing Base-Class Friends

The technique of explicitly qualifying a function name with its class name doesn’t work for friend functions because a friend function doesn’t belong to a class. However, you can use an explicit type cast to the base class to invoke the correct functions. This is basically the same technique used to access a base-class object in a class method, but with friends you have a name for the Student object, so the code uses the name instead of *this. For example, consider the following friend function definition:

ostream & operator<<(ostream & os, const Student & stu)
{
      os << "Scores for " << (const string &) stu << ": ";
...

}

If plato is a Student object, then the statement

cout << plato;

invokes that function, with stu being a reference to plato and os being a reference to cout. Within the code, the type cast in

os << "Scores for " << (const string &) stu << ": ";

explicitly converts stu to a reference to a type string object and that matches the operator<<(ostream &, const string &) function.

The reference stu doesn’t get converted automatically to a string reference. The fundamental reason is that with private inheritance, a reference or pointer to a base class cannot be assigned a reference or pointer to a derived class without an explicit type cast.

However, even if the example used public inheritance, it would have to use explicit type casts. One reason is that without a type cast, code like

os << stu;

matches the friend function prototype, leading to a recursive call. A second reason is that because the class uses MI, the compiler can’t tell which base class to convert to if both base classes happen to provide an operator<<() function. Listing 14.5 shows all the Student class methods, other than those defined inline in the class declaration.

Listing 14.5. studenti.cpp

image

image

Again, because the example reuses the string and valarray code, relatively little new code is needed, aside from the private helper method.

Using the Revised Student Class

Once again, it’s time to test a new class. Note that the two versions of the Student class have exactly the same public interface, so you can test the two versions with exactly the same program. The only difference is that you have to include studenti.h instead of studentc.h, and you have to link the program with studenti.cpp instead of with studentc.cpp. Listing 14.6 shows the program. Be sure to compile it along with studenti.cpp.

Listing 14.6. use_stui.cpp

image

image

Compatibility Note

image

If your system doesn’t correctly support the string friend getline() function, it won’t run this program correctly. You could modify the program to use operator>>() from Listing 14.5 instead. Because this friend function reads just one word, you could change the prompt to ask for just the last name.

Here is a sample run of the program in Listing 14.6:

image

The same input as before leads to the same output that the containment version produces.

Containment or Private Inheritance?

Given that you can model a has-a relationship either with containment or with private inheritance, which should you use? Most C++ programmers prefer containment. First, it’s easier to follow. When you look at the class declaration, you see explicitly named objects representing the contained classes, and your code can refer to these objects by name. Using inheritance makes the relationship appear more abstract. Second, inheritance can raise problems, particularly if a class inherits from more than one base class. You may have to deal with issues such as separate base classes having methods with the same name or of separate base classes sharing a common ancestor. All in all, you’re less likely to run into trouble using containment. Also, containment allows you to include more than one subobject of the same class. If a class needs three string objects, you can declare three separate string members by using the containment approach. But inheritance limits you to a single object. (It is difficult to tell objects apart when they are all nameless.)

However, private inheritance does offer features beyond those provided by containment. Suppose, for example, that a class has protected members, which could either be data members or member functions. Such members are available to derived classes but not to the world at large. If you include such a class in another class by using composition, the new class is part of the world at large, not a derived class. Hence it can’t access protected members. But using inheritance makes the new class a derived class, so it can access protected members.

Another situation that calls for using private inheritance is if you want to redefine virtual functions. Again, this is a privilege accorded to a derived class but not to a containing class. With private inheritance, the redefined functions would be usable just within the class, not publicly.

Tip

image

In general, you should use containment to model a has-a relationship. You should use private inheritance if the new class needs to access protected members in the original class or if it needs to redefine virtual functions.

Protected Inheritance

Protected inheritance is a variation on private inheritance. It uses the keyword protected when listing a base class:

class Student : protected std::string,
                protected std::valarray<double>
{...};

With protected inheritance, public and protected members of a base class become protected members of the derived class. As with private inheritance, the interface for the base class is available to the derived class but not to the outside world. The main difference between private and protected inheritance occurs when you derive another class from the derived class. With private inheritance, this third-generation class doesn’t get the internal use of the base class interface. That’s because the public base-class methods become private in the derived class, and private members and methods can’t be directly accessed by the next level of derivation. With protected inheritance, public base-class methods become protected in the second generation and so are available internally to the next level of derivation.

Table 14.1 summarizes public, private, and protected inheritance. The term implicit upcasting means that you can have a base class pointer or reference refer to a derived class object without using an explicit type cast.

Table 14.1. Varieties of Inheritance

image

Redefining Access with using

Public members of a base class become protected or private when you use protected or private derivation. Suppose you want to make a particular base-class method available publicly in the derived class. One option is to define a derived-class method that uses the base-class method. For example, suppose you want the Student class to be able to use the valarray sum() method. You can declare a sum() method in the class declaration and then define the method this way:

double Student::sum() const    // public Student method
{
    return std::valarray<double>::sum();     // use privately-inherited method
}

Then a Student object can invoke Student::sum(), which, in turn, applies the valarray<double>::sum() method to the embedded valarray object. (If the ArrayDb typedef is in scope, you can use ArrayDb instead of std::valarray<double>.)

There is an alternative to wrapping one function call in another: to use a using declaration (such as those used with namespaces) to announce that a particular base-class member can be used by the derived class, even though the derivation is private. For example, suppose you want to be able to use the valarray min() and max() methods with the Student class. In this case, in studenti.h, you can add using declarations to the public section:

class Student : private std::string, private std::valarray<double>
{
...
public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
    ...
};

The using declaration makes the valarray<double>::min() and valarray<double>::max() methods available as if they were public Student methods:

cout << "high score: " << ada[i].max() << endl;

Note that the using declaration just uses the member name—no parentheses, no function signatures, no return types. For example, to make the valarray operator[]() method available to the Student class, you’d place the following using declaration in the public section of the Student class declaration:

using std::valarray<double>::operator[];

This would make both versions (const and non-const) available. You could then remove the existing prototypes and definitions for Student::operator[](). The using declaration approach works only for inheritance and not for containment.

There is an older way to redeclare base-class methods in a privately derived class: You place the method name in the public section of the derived class. Here’s how you would do that:

class Student : private std::string, private std::valarray<double>
{
public:
    std::valarray<double>::operator[];  // redeclare as public, just use name
    ...

};

This looks like a using declaration without the using keyword. This approach is deprecated, meaning that the intention is to phase it out. So if your compiler supports the using declaration, you can use it to make a method from a private base class available to the derived class.

Multiple Inheritance

MI describes a class that has more than one immediate base class. As with single inheritance, public MI should express an is-a relationship. For example, if you have a Waiter class and a Singer class, you could derive a SingingWaiter class from the two:

class SingingWaiter : public Waiter, public Singer {...};

Note that you must qualify each base class with the keyword public. That’s because the compiler assumes private derivation unless instructed otherwise:

class SingingWaiter : public Waiter, Singer {...}; // Singer is a private base

As discussed earlier in this chapter, private and protected MI can express a has-a relationship; the studenti.h implementation of the Student class is an example. We’ll concentrate on public inheritance now.

MI can introduce new problems for programmers. The two chief problems are inheriting different methods with the same name from two different base classes and inheriting multiple instances of a class via two or more related immediate base classes. Solving these problems involves introducing a few new rules and syntax variations. Thus, using MI can be more difficult and problem-prone than using single inheritance. For this reason, many in the C++ community object strongly to MI; some want it removed from the language. Others love MI and argue that it’s very useful, even necessary, for particular projects. Still others suggest using MI cautiously and in moderation.

Let’s explore a particular example and see what the problems and solutions are. You need several classes to create an MI situation. For this example, you need to define an abstract Worker base class and derive a Waiter class and a Singer class from it. Then you can use MI to derive a SingingWaiter class from the Waiter and Singer classes (see Figure 14.3). This is a case in which a base class (Worker) is inherited via two separate derivations, which is the circumstance that causes the most difficulties with MI. You can start with declarations for the Worker, Waiter, and Singer classes, as shown in Listing 14.7.

Figure 14.3. MI with a shared ancestor.

image

Listing 14.7. Worker0.h

image

image

The class declarations in Listing 14.7 include some internal constants that represent voice types. An enumeration makes alto, contralto, and so on symbolic constants for voice types, and the static array pv holds pointers to the C-style string equivalents. The implementation file, shown in Listing 14.8, initializes this array and provides method definitions.

Listing 14.8. worker0.cpp

image

image

image

Listing 14.9 provides a brief test of the classes, using a polymorphic array of pointers.

Listing 14.9. worktest.cpp

image

Compatibility Note

image

If your system doesn’t correctly support the string friend getline() function, it won’t run this program correctly. You could modify the program to use operator>>() from Listing 14.8 instead. Because this friend function reads just one word, you could change the prompt to ask for just the last name.

Here is the output of the program in Listings 14.7, 14.8, and 14.9:

image

The design seems to work, with pointers to Waiter invoking Waiter::Show() and Waiter::Set(), and pointers to Singer invoking Singer::Show() and Singer::Set(). However, it leads to some problems if you add a SingingWaiter class derived from both the Singer class and Waiter class. In particular, you’ll need to face the following questions:

How many workers?

Which method?

How Many Workers?

Suppose you begin by publicly deriving SingingWaiter from Singer and Waiter:

class SingingWaiter: public Singer, public Waiter {...};

Because both Singer and Waiter inherit a Worker component, SingingWaiter winds up with two Worker components (see Figure 14.4).

Figure 14.4. Inheriting two base-class objects.

image

As you might expect, this raises problems. For example, ordinarily you can assign the address of a derived-class object to a base-class pointer, but this becomes ambiguous now:

SingingWaiter ed;
Worker * pw = &ed;   // ambiguous

Normally, such an assignment sets a base-class pointer to the address of the base-class object within the derived object. But ed contains two Worker objects, so there are two addresses from which to choose. You could specify which object by using a type cast:

Worker * pw1 = (Waiter *) &ed;   // the Worker in Waiter
Worker * pw2 = (Singer *) &ed;   // the Worker in Singer

This certainly complicates the technique of using an array of base-class pointers to refer to a variety of objects (polymorphism).

Having two copies of a Worker object causes other problems, too. However, the real issue is why should you have two copies of a Worker object at all? A singing waiter, like any other worker, should have just one name and one ID. When C++ added MI to its bag of tricks, it added a virtual base classes to make this possible.

Virtual Base Classes

Virtual base classes allow an object derived from multiple bases that themselves share a common base to inherit just one object of that shared base class. For this example, you would make Worker a virtual base class to Singer and Waiter by using the keyword virtual in the class declarations (virtual and public can appear in either order):

class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};

Then you would define SingingWaiter as before:

class SingingWaiter: public Singer, public  Waiter {...};

Now a SingingWaiter object will contain a single copy of a Worker object. In essence, the inherited Singer and Waiter objects share a common Worker object instead of each bringing in its own copy (see Figure 14.5). Because SingingWaiter now contains one Worker subobject, you can use polymorphism again.

Figure 14.5. Inheritance with a virtual base class.

image

Let’s look at some questions you might have:

• Why the term virtual?

• Why don’t we dispense with declaring base classes virtual and make virtual behavior the norm for MI?

• Are there any catches?

First, why the term virtual? After all, there doesn’t seem to be an obvious connection between the concepts of virtual functions and virtual base classes. It turns out that there is strong pressure from the C++ community to resist the introduction of new keywords. It would be awkward, for example, if a new keyword corresponded to the name of some important function or variable in a major program. So C++ merely recycled the keyword virtual for the new facility—a bit of keyword overloading.

Next, why don’t we dispense with declaring base classes virtual and make virtual behavior the norm for MI? First, there are cases in which you might want multiple copies of a base. Second, making a base class virtual requires that a program do some additional accounting, and you shouldn’t have to pay for that facility if you don’t need it. Third, there are the disadvantages presented in the next paragraph.

Finally, are there catches? Yes. Making virtual base classes work requires adjustments to C++ rules, and you have to code some things differently. Also, using virtual base classes may involve changing existing code. For example, adding the SingingWaiter class to the Worker hierarchy requires that you go back and add the virtual keyword to the Singer and Waiter classes.

New Constructor Rules

Having virtual base classes requires a new approach to class constructors. With nonvirtual base classes, the only constructors that can appear in an initialization list are constructors for the immediate base classes. But these constructors can, in turn, pass information on to their bases. For example, you can have the following organization of constructors:

image

A C constructor can invoke only constructors from the B class, and a B constructor can invoke only constructors from the A class. Here the C constructor uses the q value and passes the values of m and n back to the B constructor. The B constructor uses the value of m and passes the value of n back to the A constructor.

This automatic passing of information doesn’t work if Worker is a virtual base class. For example, consider the following possible constructor for the MI example:

SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
                  : Waiter(wk,p), Singer(wk,v) {}  // flawed

The problem is that automatic passing of information would pass wk to the Worker object via two separate paths (Waiter and Singer). To avoid this potential conflict, C++ disables the automatic passing of information through an intermediate class to a base class if the base class is virtual. Thus, the previous constructor will initialize the panache and voice members, but the information in the wk argument won’t get to the Waiter subobject. However, the compiler must construct a base object component before constructing derived objects; in this case, it will use the default Worker constructor.

If you want to use something other than the default constructor for a virtual base class, you need to invoke the appropriate base constructor explicitly. Thus, the constructor should look like this:

SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
                  : Worker(wk), Waiter(wk,p), Singer(wk,v) {}

Here the code explicitly invokes the Worker(const Worker &) constructor. Note that this usage is legal and often necessary for virtual base classes, and it is illegal for nonvirtual base classes.

Caution

image

If a class has an indirect virtual base class, a constructor for that class should explicitly invoke a constructor for the virtual base class unless all that is needed is the default constructor for the virtual base class.

Which Method?

In addition to introducing changes in class constructor rules, MI often requires other coding adjustments. Consider the problem of extending the Show() method to the SingingWaiter class. Because a SingingWaiter object has no new data members, you might think the class could just use the inherited methods. This brings up the first problem. Suppose you do omit a new version of Show() and try to use a SingingWaiter object to invoke an inherited Show() method:

SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Show();  // ambiguous

With single inheritance, failing to redefine Show() results in using the most recent ancestral definition. In this case, each direct ancestor has a Show() function, which makes this call ambiguous.

Caution

image

MI can result in ambiguous function calls. For example, a BadDude class could inherit two quite different Draw() methods from a Gunslinger class and a PokerPlayer class.

You can use the scope-resolution operator to clarify what you mean:

SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Singer::Show();  // use Singer version

However, a better approach is to redefine Show() for SingingWaiter and to have it specify which Show() to use. For example, if you want a SingingWaiter object to use the Singer version, you could use this:

void SingingWaiter::Show()
{
    Singer::Show();
}

This method of having the derived method call the base method works well enough for single inheritance. For example, suppose that the HeadWaiter class derives from the Waiter class. You could use a sequence of definitions like this, with each derived class adding to the information displayed by its base class:

void Worker::Show() const
{
    cout << "Name: " << fullname << " ";
    cout << "Employee ID: " << id << " ";
}

void Waiter::Show() const
{
    Worker::Show();
    cout << "Panache rating: " << panache << " ";
}
void HeadWaiter::Show() const
{
    Waiter::Show();
    cout << "Presence rating: " << presence << " ";
}

This incremental approach fails for the SingingWaiter case, however. The method

void SingingWaiter::Show()
{
    Singer::Show();
}

fails because it ignores the Waiter component. You can remedy that by called the Waiter version also:

image

This displays a person’s name and ID twice because Singer::Show() and with Waiter::Show() both call Worker::Show().

How can you fix this? One way is to use a modular approach instead of an incremental approach. That is, you an provide a method that displays only Worker components, another method that displays only Waiter components (instead of Waiter plus Worker components), and another that displays only Singer components. Then the SingingWaiter::Show() method can put those components together. For example, you could use this:

image

Similarly, the other Show() methods would be built from the appropriate Data() components.

With this approach, objects would still use the Show() method publicly. The Data() methods, on the other hand, should be internal to the classes; they should be helper methods used to facilitate the public interface. However, making the Data() methods private would prevent, say, Waiter code from using Worker::Data(). Here is just the kind of situation for which the protected access class is useful. If the Data() methods are protected, they can by used internally by all the classes in the hierarchy while being kept hidden from the outside world.

Another approach would be to make all the data components protected instead of private, but using protected methods instead of protected data puts tighter control on the allowable access to the data.

The Set() methods, which solicit data for setting object values, present a similar problem. For example, SingingWaiter::Set() should ask for Worker information once, not twice. The same solution used for Show() works. You can provide protected Get() methods that solicit information for just a single class, and then you can put together Set() methods that use the Get() methods as building blocks.

In short, introducing MI with a shared ancestor requires introducing virtual base classes, altering the rules for constructor initialization lists, and possibly recoding the classes if they were written with MI in mind. Listing 14.10 shows the modified class declarations with these changes institutes, and Listing 14.11 shows the implementation.

Listing 14.10. workermi.h

image

image

image

Listing 14.11. workermi.cpp

image

image

image

image

Of course, curiosity demands that you test these classes, and Listing 14.12 provides code to do so. Note that the program makes use of polymorphism by assigning the addresses of various kinds of classes to base-class pointers. Also, the program uses the C-style string library function strchr() in the following test:

while (strchr("wstq", choice) == NULL)

This function returns the address of the first occurrence of the choice character value in the string "wstq"; the function returns the NULL pointer if the character isn’t found. This test is simpler to write than an if statement that compares choice to each letter individually.

Be sure to compile Listing 14.12 along with workermi.cpp.

Listing 14.12. workmi.cpp

image

Compatibility Note

image

If your system doesn’t correctly support the string friend getline() function, it won’t run this program correctly. You could modify the program to use operator>>() from Listing 14.11 instead. Because this friend function reads just one word, you could change the prompt to ask for just the last name.

Here is a sample run of the program in Listings 14.10, 14.11, and 14.12:

image

image

Let’s look at a few more matters concerning MI.

Mixed Virtual and Nonvirtual Bases

Let’s consider again the case of a derived class that inherits a base class by more than one route. If the base class is virtual, the derived class contains one subobject of the base class. If the base class is not virtual, the derived class contains multiple subobjects. What if there is a mixture? Suppose, for example, that class B is a virtual base class to classes C and D and a nonvirtual base class to classes X and Y. Furthermore, suppose class M is derived from C, D, X, and Y. In this case, class M contains one class B subobject for all the virtually derived ancestors (that is, classes C and D) and a separate class B subobject for each nonvirtual ancestor (that is, classes X and Y). So, all told, it would contain three class B subobjects. When a class inherits a particular base class through several virtual paths and several nonvirtual paths, the class has one base-class subobject to represent all the virtual paths and a separate base-class subobject to represent each nonvirtual path.

Virtual Base Classes and Dominance

Using virtual base classes alters how C++ resolves ambiguities. With nonvirtual base classes, the rules are simple. If a class inherits two or more members (data or methods) with the same name from different classes, using that name without qualifying it with a class name is ambiguous. If virtual base classes are involved, however, such a use may or may not be ambiguous. In this case, if one name dominates all others, it can be used unambiguously without a qualifier.

So how does one member name dominate another? A name in a derived class dominates the same name in any ancestor class, whether direct or indirect. For example, consider the following definitions:

image

Here the definition of q() in class C dominates the definition in class B because C is derived from B. Thus, methods in F can use q() to denote C::q(). On the other hand, neither definition of omb() dominates the other because neither C nor E is a base class to the other. Therefore, an attempt by F to use an unqualified omb() would be ambiguous.

The virtual ambiguity rules pay no attention to access rules. That is, even though E::omb() is private and hence not directly accessible to class F, using omb() is ambiguous. Similarly, even if C::q() were private, it would dominate D::q(). In that case, you could call B::q() in class F, but an unqualified q() for that would refer to the inaccessible C::q().

MI Synopsis

First, let’s review MI without virtual base classes. This form of MI imposes no new rules. However, if a class inherits two members with the same name but from different classes, you need to use class qualifiers in the derived class to distinguish between the two members. That is, methods in the BadDude class, derived from Gunslinger and PokerPlayer, would use Gunslinger::draw() and PokerPlayer::draw() to distinguish between draw() methods inherited from the two classes. Otherwise, the compiler should complain about ambiguous usage.

If one class inherits from a nonvirtual base class by more than one route, then the class inherits one base class object for each nonvirtual instance of the base class. In some cases, this may be what you want, but more often, multiple instances of a base class are a problem.

Next, let’s look at MI with virtual base classes. A class becomes a virtual base class when a derived class uses the keyword virtual when indicating derivation:

class marketing : public virtual reality { ... };

The main change, and the reason for virtual base classes, is that a class that inherits from one or more instances of a virtual base class inherits just one base class object. Implementing this feature entails other requirements:

• A derived class with an indirect virtual base class should have its constructors invoke the indirect base class constructors directly, which is illegal for indirect nonvirtual base classes.

• Name ambiguity is resolved via the dominance rule.

As you can see, MI can introduce programming complexities. However, most of these complexities arise when a derived class inherits from the same base class by more than one route. If you avoid that situation, about the only thing you need to watch for is qualifying inherited names when necessary.

Class Templates

Inheritance (public, private, or protected) and containment aren’t always the solution when you want to reuse code. Consider, for example, the Stack class (see Chapter 10) and the Queue class (see Chapter 12). These are examples of container classes, which are classes designed to hold other objects or data types. The Stack class from Chapter 10, for example, stores unsigned long values. You could just as easily define a stack class for storing double values or string objects. The code would be identical except for the type of object stored. However, rather than write new class declarations, it would be nice if you could define a stack in a generic (that is, type-independent) fashion and then provide a specific type as a parameter to the class. Then you could use the same generic code to produce stacks of different kinds of values. In Chapter 10, the Stack example uses typedef as a first pass at dealing with this desire. However, that approach has a couple drawbacks. First, you have to edit the header file each time you change the type. Second, you can use the technique to generate just one kind of stack per program. That is, you can’t have a typedef represent two different types simultaneously, so you can’t use the method to define a stack of ints and a stack of strings in the same program.

C++’s class templates provide a better way to generate generic class declarations. (C++ originally did not support templates, and, since their introduction, templates have continued to evolve, so it is possible that your compiler may not support all the features presented here.) Templates provide parameterized types—that is, they are capable of passing a type name as an argument to a recipe for building a class or a function. By feeding the type name int to a Queue template, for example, you can get the compiler to construct a Queue class for queuing ints.

The C++ library provides several template classes. Earlier in this chapter, you worked with the valarray template class. C++’s Standard Template Library (STL), which Chapter 16 discusses in part, provides powerful and flexible template implementations of several container classes. This chapter explores designs of a more elementary nature.

Defining a Class Template

Let’s use the Stack class from Chapter 10 as a model from which to build a template. Here’s the original class declaration:

image

The template approach will replace the Stack definition with a template definition and the Stack member functions with template member functions. As with template functions, you preface a template class with code that has the following form:

template <class Type>

The keyword template informs the compiler that you’re about to define a template. The part in angle brackets is analogous to an argument list to a function. You can think of the keyword class as serving as a type name for a variable that accepts a type as a value, and of Type as representing a name for this variable.

Using class here doesn’t mean that Type must be a class; it just means that Type serves as a generic type specifier for which a real type will be substituted when the template is used. Newer C++ implementations allow you to use the less confusing keyword typename instead of class in this context:

template <typename Type>  // newer choice

You can use your choice of generic type name in the Type position; the name rules are the same as those for any other identifier. Popular choices include T and Type; in this case, you should use the latter. When a template is invoked, Type will be replaced with a specific type value, such as int or string. Within the template definition, you can use the generic type name to identify the type to be stored in the stack. For the Stack case, that would mean using Type wherever the old declaration formerly used the typedef identifier Item. For example,

Item items[MAX];  // holds stack items

becomes the following:

Type items[MAX];  // holds stack items

Similarly, you can replace the class methods of the original class with template member functions. Each function heading will be prefaced with the same template announcement:

template <class Type>

Again, you should replace the typedef identifier Item with the generic type name Type. One more change is that you need to change the class qualifier from Stack:: to Stack<Type>::. For example,

bool Stack::push(const Item & item)
{
...
}

becomes the following:

template <class Type>                     // or template <typename Type>
bool Stack<Type>::push(const Type & item)
{
...
}

If you define a method within the class declaration (an inline definition), you can omit the template preface and the class qualifier.

Listing 14.13 shows the combined class and member function templates. It’s important to realize that these templates are not class and member function definitions. Rather, they are instructions to the C++ compiler about how to generate class and member function definitions. A particular actualization of a template, such as a stack class for handling string objects, is called an instantiation or a specialization. Unless you have a compiler that has implemented the new export keyword, placing the template member functions in a separate implementation file won’t work. Because the templates aren’t functions, they can’t be compiled separately. Templates have to be used in conjunction with requests for particular instantiations of templates. The simplest way to make this work is to place all the template information in a header file and to include the header file in the file that will use the templates.

Listing 14.13. stacktp.h

image

If your compiler does implement the new export keyword, you can place the template method definitions in a separate file, provided that you preface each template declaration with export:

// stacktp.h -- a stack template
#ifndef STACKTP_H_
#define STACKTP_H_
export template <class Type>    // preface with export
class Stack
{
...
};

Then you could follow the same convention used for ordinary classes:

  1. Place the template class declaration (with the keyword export) in a header file and use the #include directive to make the declaration available to a program.
  2. Place the template class method definitions in a source code file, include the header file in that file, and use a project file or equivalent to make the definitions available to a program.

Using a Template Class

Merely including a template in a program doesn’t generate a template class. You have to ask for an instantiation. To do this, you declare an object of the template class type, replacing the generic type name with the particular type you want. For example, here’s how you would create two stacks, one for stacking ints and one for stacking string objects:

Stack<int> kernels;      // create a stack of ints
Stack<string> colonels;  // create a stack of string objects

Seeing these two declarations, the compiler will follow the Stack<Type> template to generate two separate class declarations and two separate sets of class methods. The Stack<int> class declaration will replace Type throughout with int, and the Stack<string> class declaration will replace Type throughout with string. Of course, the algorithms you use have to be consistent with the types. The Stack class, for example, assumes that you can assign one item to another. This assumption is true for basic types, structures, and classes (unless you make the assignment operator private) but not for arrays.

Generic type identifiers such as Type in the example are called type parameters, meaning that they act something like variables, but instead of assigning a numeric value to a type parameter, you assign a type to it. So in the kernel declaration, the type parameter Type has the value int.

Notice that you have to provide the desired type explicitly. This is different from ordinary function templates, for which the compiler can use the argument types to a function to figure out what kind of function to generate:

Template <class T>
void simple(T t) { cout << t << ' ';}
...
simple(2);       // generate void simple(int)
simple("two")    // generate void simple(char *)

Listing 14.14 modifies the original stack-testing program (Listing 10.12) to use string purchase order IDs instead of unsigned long values.

Listing 14.14. stacktem.cpp

image

image

Compatibility Note

image

You should use the older ctype.h header file if your C++ implementation doesn’t provide cctype.

Here’s a sample run of the program in Listing 14.14:

image

A Closer Look at the Template Class

You can use a built-in type or a class object as the type for the Stack<Type> class template. What about a pointer? For example, can you use a pointer to a char instead of a string object in Listing 14.14? After all, such pointers are the built-in way for handling C++ strings. The answer is that you can create a stack of pointers, but it won’t work very well without major modifications in the program. The compiler can create the class, but it’s your task to see that it’s used sensibly. Let’s see why such a stack of pointers doesn’t work very well with Listing 14.14, and then let’s look at an example where a stack of pointers is useful.

Using a Stack of Pointers Incorrectly

Let’s quickly look at three simple, but flawed, attempts to adapt Listing 14.14 to use a stack of pointers. These attempts illustrate the lesson that you should keep the design of a template in mind and not just use it blindly. All three examples begin with this perfectly valid invocation of the Stack<Type> template:

Stack<char *> st; // create a stack for pointers-to-char

Version 1 then replaces

string po;

with

char * po;

The idea is to use a char pointer instead of a string object to receive the keyboard input. This approach fails immediately because merely creating a pointer doesn’t create space to hold the input strings. (The program would compile, but it would quite possibly crash after cin tried to store input in some inappropriate location.)

Version 2 replaces

string po;

with

char po[40];

This allocates space for an input string. Furthermore, po is of type char *, so it can be placed on the stack. But an array is fundamentally at odds with the assumptions made for the pop() method:

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

First, the reference variable item has to refer to an lvalue of some sort, not to an array name. Second, the code assumes that you can assign to item. Even if item could refer to an array, you can’t assign to an array name. So this approach fails, too.

Version 3 replaces

string po;

with

char * po = new char[40];

This allocates space for an input string. Furthermore, po is a variable and hence compatible with the code for pop(). Here, however, you come up against the most fundamental problem: There is only one po variable, and it always points to the same memory location. True, the contents of the memory change each time a new string is read, but every push operation puts exactly the same address onto the stack. So when you pop the stack, you always get the same address back, and it always refers to the last string read into memory. In particular, the stack is not storing each new string separately as it comes in, and it serves no useful purpose.

Using a Stack of Pointers Correctly

One way to use a stack of pointers is to have the calling program provide an array of pointers, with each pointer pointing to a different string. Putting these pointers on a stack then makes sense because each pointer will refer to a different string. Note that it is the responsibility of the calling program, not the stack, to create the separate pointers. The stack’s job is to manage the pointers, not create them.

For example, suppose you have to simulate the following situation. Someone has delivered a cart of folders to Plodson. If Plodson’s in-basket is empty, he removes the top folder from the cart and places it in his in-basket. If his in-basket is full, Plodson removes the top file from the basket, processes it, and places it in his out-basket. If the in-basket is neither empty nor full, Plodson may process the top file in the in-basket, or he may take the next file from the cart and put it into his in-basket. In what he secretly regards as a bit of madcap self-expression, he flips a coin to decide which of these actions to take. You’d like to investigate the effects of his method on the original file order.

You can model this with an array of pointers to strings representing the files on the cart. Each string will contain the name of the person described by the file. You can use a stack to represent the in-basket, and you can use a second array of pointers to represent the out-basket. Adding a file to the in-basket is represented by pushing a pointer from the input array onto the stack, and processing a file is represented by popping an item from the stack and adding it to the out-basket.

Given the importance of examining all aspects of this problem, it would be useful to be able to try different stack sizes. Listing 14.15 redefines the Stack<Type> class slightly so that the Stack constructor accepts an optional size argument. This involves using a dynamic array internally, so the class now needs a destructor, a copy constructor, and an assignment operator. Also, the definition shortens the code by making several of the methods inline.

Listing 14.15. stcktp1.h

image

image

image

Compatibility Note

image

Some C++ implementations might not recognize explicit.

Notice that the prototype declares the return type for the assignment operator function to be a reference to Stack, and the actual template function definition identifies the type as Stack<Type>. The former is an abbreviation for the latter, but it can be used only within the class scope. That is, you can use Stack inside the template declaration and inside the template function definitions, but outside the class, as when identifying return types and when using the scope-resolution operator, you need to use the full Stack<Type> form.

The program in Listing 14.16 uses the new stack template to implement the Plodson simulation. It uses rand(), srand(), and time() in the same way previous simulations have used them to generate random numbers. In this case, randomly generating a 0 or a 1 simulates the coin toss.

Listing 14.16. stkoptr1.cpp

image

image

Compatibility Note

image

Some C++ implementations require stdlib.h instead of cstdlib and time.h instead of ctime.

Two sample runs of the program in Listing 14.16 follow (note that, thanks to the randomizing feature, the final file ordering can differ quite a bit from one trial to the next, even when the stack size is kept unaltered):

Please enter stack size: 5
2: Kiki Ishtar
1: Hank Gilgamesh
3: Betty Rocker
5: Wolfgang Kibble
4: Ian Flagranti
7: Joy Almondo
9: Juan Moore
8: Xaverie Paprika
6: Portia Koop
10: Misha Mache
Bye

Please enter stack size: 5
3: Betty Rocker
5: Wolfgang Kibble
6: Portia Koop
4: Ian Flagranti
8: Xaverie Paprika
9: Juan Moore
10: Misha Mache

7: Joy Almondo
2: Kiki Ishtar
1: Hank Gilgamesh
Bye

Program Notes

The strings in Listing 14.16 never move. Pushing a string onto the stack really creates a new pointer to an existing string. That is, it creates a pointer whose value is the address of an existing string. And popping a string off the stack copies that address value into the out array.

The program uses const char * as a type because the array of pointers is initialized to a set of string constants.

What effect does the stack destructor have on the strings? None. The class constructor uses new to create an array for holding pointers. The class destructor eliminates that array, not the strings to which the array elements pointed.

An Array Template Example and Non-Type Arguments

Templates are frequently used for container classes because the idea of type parameters matches well with the need to apply a common storage plan to a variety of types. Indeed, the desire to provide reusable code for container classes was the main motivation for introducing templates, so let’s look at another example and explore a few more facets of template design and use. In particular, let’s look at non-type, or expression, arguments and at using an array to handle an inheritance family.

Let’s begin with a simple array template that lets you specify an array size. One technique, which the last version of the Stack template uses, is to use a dynamic array within the class and a constructor argument to provide the number of elements. Another approach is to use a template argument to provide the size for a regular array. Listing 14.17 shows how this can be done.

Listing 14.17. arraytp.h

image

image

Note the template heading in Listing 14.17:

template <class T, int n>

The keyword class (or, equivalently in this context, typename) identifies T as a type parameter, or type argument. int identifies n as being an int type. This second kind of parameter, one that specifies a particular type instead of acting as a generic name for a type, is called a non-type, or expression, argument. Suppose you have the following declaration:

ArrayTP<double, 12> eggweights;

This causes the compiler to define a class called ArrayTP<double,12> and to create an eggweights object of that class. When defining the class, the compiler replaces T with double and n with 12.

Expression arguments have some restrictions. An expression argument can be an integral type, an enumeration type, a reference, or a pointer. Thus, double m is ruled out, but double & rm and double * pm are allowed. Also, the template code can’t alter the value of the argument or take its address. Thus, in the ArrayTP template, expressions such as n++ or &n would not be allowed. Also, when you instantiate a template, the value used for the expression argument should be a constant expression.

This approach for sizing an array has one advantage over the constructor approach used in Stack. The constructor approach uses heap memory managed by new and delete, whereas the expression argument approach uses the memory stack maintained for automatic variables. This provides faster execution time, particularly if you have a lot of small arrays.

The main drawback to the expression argument approach is that each array size generates its own template. That is, the declarations

ArrayTP<double, 12> eggweights;
ArrayTP(double, 13> donuts;

generate two separate class declarations. But the declarations

Stack<int> eggs(12);
Stack<int> dunkers(13);

generate just one class declaration, and the size information is passed to the constructor for that class.

Another difference is that the constructor approach is more versatile because the array size is stored as a class member rather than being hard-coded into the definition. This makes it possible, for example, to define assignment from an array of one size to an array of another size or to build a class that allows resizable arrays.

Template Versatility

You can apply the same techniques to template classes as you do to regular classes. Template classes can serve as base classes, and they can be component classes. They can themselves be type arguments to other templates. For example, you can implement a stack template by using an array template. Or you can have an array template that is used to construct an array whose elements are stacks based on a stack template. That is, you can have code along the following lines:

template <class T>
class Array
{
private:
    T entry;
    ...
};

template <class Type>
class GrowArray : public Array<Type> {...};  // inheritance

template <class Tp>
class Stack
{
       Array<Tp> ar;       // use an Array<> as a component
       ...
};
...
Array < Stack<int> > asi;  // an array of stacks of int

In the last statement, you must separate the two > symbols by at least one white-space character in order to avoid confusion with the >> operator.

Using a Template Recursively

Another example of template versatility is that you can use templates recursively. For example, given the earlier definition of an array template, you can use it as follows:

ArrayTP< ArrayTP<int,5>, 10> twodee;

This makes twodee an array of 10 elements, each of which is an array of five ints. The equivalent ordinary array would have this declaration:

int twodee[10][5];

Note that the syntax for templates presents the dimensions in the opposite order from that of the equivalent ordinary two-dimensional array. The program in Listing 14.18 tries this idea. It also uses the ArrayTP template to create one-dimensional arrays to hold the sum and average value of each of the 10 sets of five numbers. The method call cout.width(2) causes the next item to be displayed to use a field width of two characters, unless a larger width is needed to show the whole number.

Listing 14.18. twod.cpp

image

The output of the program in Listing 14.18 has one line for each of the 10 elements of twodee, each of which is a five-element array. Each line shows the values, sum, and average of an element of twodee:

1  2  3  4  5 : sum =  15, average = 1.5
2  4  6  8 10 : sum =  30, average = 3
3  6  9 12 15 : sum =  45, average = 4.5
4  8 12 16 20 : sum =  60, average = 6
5 10 15 20 25 : sum =  75, average = 7.5
6 12 18 24 30 : sum =  90, average = 9
7 14 21 28 35 : sum = 105, average = 10.5
8 16 24 32 40 : sum = 120, average = 12
9 18 27 36 45 : sum = 135, average = 13.5
10 20 30 40 50 : sum = 150, average = 15
Done.

Using More Than One Type Parameter

You can have a template with more than one type parameter. For example, suppose you want a class that holds two kinds of values. You can create and use a Pair template class for holding two disparate values. (Incidentally, the STL provides a similar template called pair.) The short program in Listing 14.19 shows an example. In it, the first() const and second() const methods report the stored values, and the first() and second() methods, by virtue of returning references to the Pair data members, allow you to reset the stored values by using assignment.

Listing 14.19. pairs.cpp

image

image

One thing to note about Listing 14.19 is that in main(), you have to use Pair<string,int> to invoke the constructors and as an argument for sizeof. That’s because Pair<string,int> and not Pair is the class name. Also, Pair<char *,double> would be the name of an entirely different class.

Here’s the output of the program in Listing 14.19:

Rating:  Eatery
5:       The Purple Duke
4:       Jake's Frisco Al Fresco
5:       Mont Souffle
3:       Gertie's Eats
Oops! Revised rating:
6:       Gertie's Fab Eats

Default Type Template Parameters

Another new class template feature is that you can provide default values for type parameters:

template <class T1, class T2 = int> class Topo {...};

This causes the compiler to use int for the type T2 if a value for T2 is omitted:

Topo<double, double> m1; // T1 is double, T2 is double
Topo<double> m2;         // T1 is double, T2 is int

The STL (discussed in Chapter 16) often uses this feature, with the default type being a class.

Although you can provide default values for class template type parameters, you can’t do so for function template parameters. However, you can provide default values for non-type parameters for both class and function templates.

Template Specializations

Class templates are like function templates in that you can have implicit instantiations, explicit instantiations, and explicit specializations, collectively known as specializations. That is, a template describes a class in terms of a general type, whereas a specialization is a class declaration generated by using a specific type.

Implicit Instantiations

The template examples you have seen so far in this chapter use implicit instantiations. That is, they declare one or more objects indicating the desired type, and the compiler generates a specialized class definition, using the recipe provided by the general template:

ArrayTP<int, 100> stuff; // implicit instantiation

The compiler doesn’t generate an implicit instantiation of the class until it needs an object:

ArrayTP<double, 30> * pt;     // a pointer, no object needed yet
pt = new ArrayTP<double, 30>; // now an object is needed

The second statement causes the compiler to generate a class definition and also an object that is created according to that definition.

Explicit Instantiations

The compiler generates an explicit instantiation of a class declaration when you declare a class by using the keyword template and indicating the desired type or types. The declaration should be in the same namespace as the template definition. For example, the declaration

template class ArrayTP<string, 100>; // generate ArrayTP<string, 100> class

declares ArrayTP<string, 100> to be a class. In this case, the compiler generates the class definition, including method definitions, even though no object of the class has yet been created or mentioned. Just as with the implicit instantiation, the general template is used as a guide to generate the specialization.

Explicit Specializations

An explicit specialization is a definition for a particular type or types that is to be used instead of the general template. Sometimes you might need or want to modify a template to behave differently when instantiated for a particular type; in that case, you can create an explicit specialization. Suppose, for example, that you’ve defined a template for a class that represents a sorted array for which items are sorted as they are added to the array:

template <class T>
class SortedArray
{
     ...// details omitted
};

Also, suppose the template uses the > operator to compare values. This works well for numbers. It will work if T represents a class type, too, provided that you’ve defined a T::operator>() method. But it won’t work if T is a string represented by type char *. Actually, the template will work, but the strings will wind up sorted by address rather than alphabetically. What is needed is a class definition that uses strcmp() instead of >. In such a case, you can provide an explicit template specialization. This takes the form of a template defined for one specific type instead of for a general type. When faced with the choice of a specialized template and a general template that both match an instantiation request, the compiler uses the specialized version.

A specialized class template definition has the following form:

template <> class Classname < specialized-type-name> { ... };

Older compilers may only recognize the older form, which dispenses with the template <>:

class Classname < specialized-type-name> { ... };

To provide a SortedArray template specialized for the char * type, using the new notation, you would use code like the following:

template <> class SortedArray<char *>
{
     ...// details omitted
};

Here the implementation code would use strcmp() instead of > to compare array values. Now, requests for a SortedArray template of char * will use this specialized definition instead of the more general template definition:

SortedArray<int> scores;   // use general definition
SortedArray<char *> dates; // use specialized definition

Partial Specializations

C++ allows for partial specializations, which partially restrict the generality of a template. For example, a partial specialization can provide a specific type for one of the type parameters:

// general template
    template <class T1, class T2> class Pair {...};
// specialization with T2 set to int
    template <class T1> class Pair<T1, int> {...};

The <> following the keyword template declares the type parameters that are still unspecialized. So the second declaration specializes T2 to int but leaves T1 open. Note that specifying all the types leads to an empty bracket pair and a complete explicit specialization:

// specialization with T1 and T2 set to int
    template <> class Pair<int, int> {...};

The compiler uses the most specialized template if there is a choice:

Pair<double, double> p1; // use general Pair template
Pair<double, int> p2;    // use Pair<T1, int> partial specialization
Pair<int, int> p3;       // use Pair<int, int> explicit specialization

Or you can partially specialize an existing template by providing a special version for pointers:

template<class T>      // general version
class Feeb { ... };
template<class T*>    // pointer partial specialization
class Feeb { ... };     // modified code

If you provide a non-pointer type, the compiler uses the general version; if you provide a pointer, the compiler uses the pointer specialization:

Feeb<char> fb1;        // use general Feeb template, T is char
Feeb<char *> fb2;      // use Feeb T* specialization, T is char

Without the partial specialization, the second declaration would use the general template, interpreting T as type char *. With the partial specialization, it uses the specialized template, interpreting T as char.

The partial specialization feature allows for making a variety of restrictions. For example, you can use the following:

// general template
    template <class T1, class T2, class T3> class Trio{...};
// specialization with T3 set to T2
    template <class T1, class T2> class Trio<T1, T2, T2> {...};
// specialization with T3 and T2 set to T1*
    template <class T1> class Trio<T1, T1*, T1*> {...};

Given these declarations, the compiler would make the following choices:

Trio<int, short, char *> t1; // use general template
Trio<int, short> t2; // use Trio<T1, T2, T2>
Trio<char, char *, char *> t3; use Trio<T1, T1*, T1*>

Member Templates

Another of the more recent additions to C++ template support is that a template can be a member of a structure, class, or template class. The STL requires this feature to fully implement its design. Listing 14.20 provides a short example of a template class with a nested template class and a template function as members.

Listing 14.20. tempmemb.cpp

image

The hold template is declared in the private section in Listing 14.20, so it is accessible only within the beta class scope. The beta class uses the hold template to declare two data members:

hold<T> q;        // template object
hold<int> n;      // template object

n is a hold object based on the int type, and the q member is a hold object based on the T type (the beta template parameter). In main(), the declaration

beta<double> guy(3.5, 3);

makes T represent double, making q type hold<double>.

The blab() method has one type (U) that is determined implicitly by the argument value when the method is called and one type (T) that is determined by the instantiation type of the object. In this example, the declaration for guy sets T to type double, and the first argument in the method call in

cout << guy.blab(10, 2.5) << endl;

sets U to type int, matching the value 10. Thus, although the automatic type conversions brought about by mixed types cause the calculation in blab() to be done as type double, the return value, being type U, is an int, as the following program output shows:

3.5
3
28
Done

If you replace 10 with 10.0 in the call to guy.blab(), U is set to double, making the return type double, and you get this output instead:

3.5
3
28.2609
Done

As mentioned previously, the type of the second parameter is set to double by the declaration of the guy object. Unlike the first parameter, then, the type of the second parameter is not set by the function call. For instance, the statement

cout << guy.blab(10, 3) << endl;

would still implement blah() as blah(int, double), and the 3 would be converted to type double by the usual function prototype rules.

You can declare the hold class and blah method in the beta template and define them outside the beta template. However, because implementing templates is still a new process, it may be more difficult to get the compiler to accept these forms. Some compilers may not accept template members at all, and others that accept them as shown in Listing 14.20 don’t accept definitions outside the class. However, if your compiler is willing and able, here’s how defining the template methods outside the beta template would look:

image

The definitions have to identify T, V, and U as template parameters. Because the templates are nested, you have to use the

template <typename T>
  template <typename V>

syntax instead of the

template<typename T, typename V>

syntax. The definitions also must indicate that hold and blab are members of the beta<T> class, and they use the scope-resolution operator to do so.

Templates as Parameters

You’ve seen that a template can have type parameters, such as typename T, and non-type parameters, such as int n. A template can also have a parameter that is itself a template. Such parameters are yet another recent template feature addition that is used to implement the STL.

Listing 14.21 shows an example for which the template parameter is template <typename T> class Thing. Here template <typename T> class is the type and Thing is the parameter. What does this imply? Suppose you have this declaration:

Crab<King> legs;

For this to be accepted, the template argument King has to be a template class whose declaration matches that of the template parameter Thing:

template <typename T>
class King {...};

The Crab declaration declares two objects:

Thing<int> s1;
Thing<double> s2;

The previous declaration for legs would then result in substituting King<int> for Thing<int> and King<double> for Thing<double>. However, Listing 14.21 has this declaration:

Crab<Stack> nebula;

Hence, in this case, Thing<int> is instantiated as Stack<int>, and Thing<double> is instantiated as Stack<double>. In short, the template parameter Thing is replaced by whatever template type is used as a template argument in declaring a Crab object.

The Crab class declaration makes three further assumptions about the template class represented by Thing. The class should have a push() method, the class should have a pop() method, and these methods should have a particular interface. The Crab class can use any template class that matches the Thing type declaration and that has the prerequisite push() and pop() methods. This chapter happens to have one such class, the Stack template defined in stacktp.h, so the example uses that class.

Listing 14.21. tempparm.cpp

image

Here is a sample run of the program in Listing 14.21:

Enter int double pairs, such as 4 3.5 (0 0 to end):
50 22.48
25 33.87
60 19.12
0 0
60, 19.12
25, 33.87
50, 22.48
Done.

You can mix template parameters with regular parameters. For example, the Crab class declaration could start out like this:

template <template <typename T> class Thing, typename U, typename V>
class Crab
{
private:
    Thing<U> s1;
    Thing<V> s2;
...

Now the types to be stored in the members s1 and s2 are generic types instead of hard-coded types. This would require the declaration of nebula in the program to be changed to this:

Crab<Stack, int, double> nebula; // T=Stack, U=int, V=double

The template parameter T represents a template type, and the type parameters U and V represent non-template types.

Template Classes and Friends

Template class declarations can have friends, too. You can classify friends of templates into three categories:

• Non-template friends

• Bound template friends, meaning the type of the friend is determined by the type of the class when a class is instantiated

• Unbound template friends, meaning that all specializations of the friend are friends to each specialization of the class

Let’s look at examples of each.

Non-Template Friend Functions to Template Classes

Let’s declare an ordinary function in a template class as a friend:

template <class T>
class HasFriend
{
      friend void counts();    // friend to all HasFriend instantiations
      ...
};

This declaration makes the counts() function a friend to all possible instantiations of the template. For example, it would be a friend to the HasFriend<int> class and the HasFriend<string> class.

The counts() function is not invoked by an object (it’s a friend, not a member function) and it has no object parameters, so how does it access a HasFriend object? There are several possibilities. It could access a global object; it could access nonglobal objects by using a global pointer; it could create its own objects; and it could access static data members of a template class, which exist separately from an object.

Suppose you want to provide a template class argument to a friend function. Can you have a friend declaration like this, for example?

friend void report(HasFriend &);   // possible?

The answer is no. The reason is that there is no such thing as a HasFriend object. There are only particular specializations, such as HasFriend<short>. To provide a template class argument, then, you have to indicate a specialization. For example, you can use this:

template <class T>
class HasFriend
{
    friend void report(HasFriend<T> &); // bound template friend
    ...
};

To understand what this does, imagine the specialization produced if you declare an object of a particular type:

HasFriend<int> hf;

The compiler would replace the template parameter T with int, giving the friend declaration this form:

class HasFriend<int>
{
    friend void report(HasFriend<int> &); // bound template friend
    ...
};

That is, report() with a HasFriend<int> parameter becomes a friend to the HasFriend<int> class. Similarly, report() with a HasFriend<double> parameter would be an overloaded version of report() that is a friend to the HasFriend<double> class.

Note that report() is not itself a template function; it just has a parameter that is a template. This means that you have to define explicit specializations for the friends you plan to use:

void report(HasFriend<short> &) { ... };  // explicit specialization for short
void report(HasFriend<int> &) { ... };    // explicit specialization for int

Listing 14.22 illustrates these points. The HasFriend template has a static member ct. Note that this means that each particular specialization of the class has its own static member. The counts() method, which is a friend to all HasFriend specializations, reports the value of ct for two particular specializations: HasFriend<int> and HasFriend<double>. The program also provides two report() functions, each of which is a friend to one particular HasFriend specialization.

Listing 14.22. frnd2tmp.cpp

image

image

Here is the output of the program in Listing 14.22:

No objects declared: int count: 0; double count: 0
After hfi1 declared: int count: 1; double count: 0
After hfi2 declared: int count: 2; double count: 0

After hfdb declared: int count: 2; double count: 1
HasFriend<int>: 10
HasFriend<int>: 20
HasFriend<double>: 10.5

Compatibility Note

image

Several C++ compilers have trouble with friends to templates.

Bound Template Friend Functions to Template Classes

You can modify the preceding example by making the friend functions templates themselves. In particular, you can set things up for bound template friends, so each specialization of a class gets a matching specialization for a friend. The technique is a bit more complex than for non-template friends and involves three steps.

For the first step, you declare each template function before the class definition.

template <typename T> void counts();
template <typename T> void report(T &);

Next, you declare the templates again as friends inside the function. These statements declare specializations based on the class template parameter type:

template <typename TT>
class HasFriendT
{
...
    friend void counts<TT>();
    friend void report<>(HasFriendT<TT> &);
};

The <> in the declarations identifies these as template specializations. In the case of report(), the <> can be left empty because the template type argument

HasFriendT<TT>

can be deduced from the function argument. You could, however, use

report<HasFriendT<TT> >(HasFriendT<TT> &)

instead. However, the counts() function has no parameters, so you have to use the template argument syntax (<TT>) to indicate its specialization. Note, too, that TT is the parameter type for the HasFriendT class.

Again, the best way to understand these declarations is to imagine what they become when you declare an object of a particular specialization. For example, suppose you declare this object:

HasFriendT<int> squack;

Then the compiler substitutes int for TT and generates the following class definition:

class HasFriendT<int>
{
...
    friend void counts<int>();
    friend void report<>(HasFriendT<int> &);
};

One specialization is based on TT, which becomes int, and the other is based on HasFriendT<TT>, which becomes HasFriendT<int>. Thus, the template specializations counts<int>() and report<HasFriendT<int> >() are declared as friends to the HasFriendT<int> class.

The third requirement the program must meet is to provide template definitions for the friends. Listing 14.23 illustrates these three aspects. Note that Listing 14.22 has one count() function that is a friend to all HasFriend classes, whereas Listing 14.23 has two count() functions, one of which is a friend to each of the instantiated class types. Because the count() function calls have no function parameter from which the compiler can deduce the desired specialization, these calls use the count<int>() and count<double>() forms to indicate the specialization. For the calls to report(), however, the compiler can use the argument type to deduce the specialization. You could use the <> form to the same effect:

report<HasFriendT<int> >(hfi2);  // same as report(hfi2);

Listing 14.23. tmp2tmp.cpp

image

image

Here is the output of the program in Listing 14.23:

template size: 4; template counts(): 0
10
20
10.5
counts<int>() output:
template size: 4; template counts(): 2
counts<double>() output:
template size: 8; template counts(): 1

As you can see, counts<double> reports a different template size from counts<int>, demonstrating that each T type now gets its own count() friend.

Unbound Template Friend Functions to Template Classes

The bound template friend functions in the preceding section are template specializations of a template declared outside a class. An int class specialization gets an int function specialization, and so on. By declaring a template inside a class, you can create unbound friend functions for which every function specialization is a friend to every class specialization. For unbound friends, the friend template type parameters are different from the template class type parameters:

template <typename T>
class ManyFriend
{
...
    template <typename C, typename D> friend void show2(C &, D &);
};

Listing 14.24 shows an example that uses an unbound friend. In it, the function call show2(hfi1, hfi2) gets matched to the following specialization:

void show2<ManyFriend<int> &, ManyFriend<int> &>
          (ManyFriend<int> & c, ManyFriend<int> & d);

Because it is a friend to all specializations of ManyFriend, this function has access to the item members of all specializations. But it only uses access to ManyFriend<int> objects.

Similarly, show2(hfd, hfi2) gets matched to this specialization:

void show2<ManyFriend<double> &, ManyFriend<int> &>
          (ManyFriend<double> & c, ManyFriend<int> & d);

It, too, is a friend to all ManyFriend specializations, and it uses its access to the item member of a ManyFriend<int> object and to the item member of a ManyFriend<double> object.

Listing 14.24. manyfrnd.cpp

image

Here’s the output of the program in Listing 14.24:

hfi1, hfi2: 10, 20
hfdb, hfi2: 10.5, 20

Compatibility Note

image

Several C++ compilers have trouble with friends to templates.

Summary

C++ provides several means for reusing code. Public inheritance, described in Chapter 13, “Class Inheritance,” enables you to model is-a relationships, with derived classes being able to reuse the code of base classes. Private and protected inheritance also let you reuse base class code, this time modeling has-a relationships. With private inheritance, public and protected members of the base class become private members of the derived class. With protected inheritance, public and protected members of the base class become protected members of the derived class. Thus, in either case, the public interface of the base class becomes an internal interface for the derived class. This is sometimes described as inheriting the implementation but not the interface because a derived object can’t explicitly use the base class interface. Thus, you can’t view a derived object as a kind of base object. Because of this, a base-class pointer or reference is not allowed to refer to a derived object without an explicit type cast.

You can also reuse class code by developing a class with members that are themselves objects. This approach, called containment, layering, or composition, also models the has-a relationship. Containment is simpler to implement and use than private or protected inheritance, so it is usually preferred. However, private and protected inheritance have slightly different capabilities. For example, inheritance allows a derived class access to protected members of a base class. Also, it allows a derived class to redefine a virtual function inherited from the base class. Because containment is not a form of inheritance, neither of these capabilities are options when you reuse class code via containment. On the other hand, containment is more suitable if you need several objects of a given class. For example, a State class could contain an array of County objects.

Multiple inheritance (MI) allows you to reuse code for more than one class in a class design. Private or protected MI models the has-a relationship, and public MI models the is-a relationship. MI can create problems with multidefined names and multi-inherited bases. You can use class qualifiers to resolve name ambiguities and virtual base classes to avoid multi-inherited bases. However, using virtual base classes introduces new rules for writing initialization lists for constructors and for resolving ambiguities.

Class templates let you create a generic class design in which a type, usually a member type, is represented by a type parameter. A typical template looks like this:

template <class T>
class Ic
{
      T v;
      ...
public:
      Ic(const T & val) : v(val) {}
      ...
};

Here T is the type parameter, and it acts as a stand-in for a real type to be specified at a later time. (This parameter can have any valid C++ name, but T and Type are common choices.) You can also use typename instead of class in this context:

template <typename T>  // same as template <class T>
class Rev {...} ;

Class definitions (instantiations) are generated when you declare a class object and specify a particular type. For example, the declaration

class Ic<short> sic;    // implicit instantiation

causes the compiler to generate a class declaration in which every occurrence of the type parameter T in the template is replaced by the actual type short in the class declaration. In this case, the class name is Ic<short>, not Ic. Ic<short> is termed a specialization of the template. In particular, it is an implicit instantiation.

An explicit instantiation occurs when you declare a specific specialization of the class, using the keyword template:

template class IC<int>;  // explicit instantiation

In this situation, the compiler uses the general template to generate an int specialization Ic<int>, even though no objects have yet been requested of that class.

You can provide explicit specializations, which are specialized class declarations that override a template definition. You just define the class, starting with template<>, and then you use the template class name, followed by angle brackets containing the type for which you want a specialization. For example, you could provide a specialized Ic class for character pointers as follows:

template <> class Ic<char *>.
{
      char * str;
      ...
public:
     Ic(const char * s) : str(s) { }
     ...
};

Then a declaration of the form

class Ic<char *> chic;

would use the specialized definition for chic rather than using the general template.

A class template can specify more than one generic type and can also have non-type parameters:

template <class T, class TT, int n>
class Pals {...};

The declaration

Pals<double, string, 6> mix;

would generate an implicit instantiation using double for T, string for TT, and 6 for n.

A class template can also have parameters that are templates:

template < template <typename T> class CL, typename U, int z>
class Trophy {...};

Here z stands for an int value, U stands for the name of a type, and CL stands for a class template declared using template <typename T>.

Class templates can be partially specialized:

template <class T> Pals<T, T, 10> {...};
template <class T, class TT> Pals<T, TT, 100> {...};
template <class T, int n> Pals <T, T*, n> {...};

The first example here creates a specialization in which both types are the same and n has the value 6. Similarly, the second creates a specialization for n equal to 100, and the third creates a specialization for which the second type is a pointer to the first type.

Template classes can be members of other classes, structures, and templates.

The goal of all these methods is to allow programmers to reuse tested code without having to copy it manually. This simplifies the programming task and makes programs more reliable.

Review Questions

1. For each of the following sets of classes, indicate whether public or private derivation is more appropriate for Column B:

image

2. Suppose you have the following definitions:

image

Given that the Gloam version of tell() should display the values of glip and fb, provide definitions for the three Gloam methods.

3. Suppose you have the following definitions:

image

Given that the Gloam version of tell() should display the values of glip and fab, provide definitions for the three Gloam methods.

4. Suppose you have the following definition, based on the Stack template of Listing 14.13 and the Worker class of Listing 14.10:

Stack<Worker *> sw;

Write out the class declaration that will be generated. Just do the class declaration, not the non-inline class methods.

5. Use the template definitions in this chapter to define the following:

• An array of string objects

• A stack of arrays of double

• An array of stacks of pointers to Worker objects

How many template class definitions are produced in Listing 14.18?

6. Describe the differences between virtual and nonvirtual base classes.

Programming Exercises

1. The Wine class has a string class object member (see Chapter 4) that holds the name of a wine and a Pair object (as discussed in this chapter) of valarray<int> objects (as discussed in this chapter). The first member of each Pair object holds the vintage years, and the second member holds the numbers of bottles owned for the corresponding particular vintage year. For example, the first valarray object of the Pair object might hold the years 1988, 1992, and 1996, and the second valarray object might hold the bottle counts 24, 48, and 144. It may be convenient for Wine to have an int member that stores the number of years. Also, some typedefs might be useful to simplify the coding:

typedef std::valarray<int> ArrayInt;
typedef Pair<ArrayInt, ArrayInt> PairArray;

Thus the PairArray type represents type Pair<std::valarray<int>, std:: valarray<int> >. Implement the Wine class by using containment. The class should have a default constructor and at least the following constructors:

// initialize label to l, number of years to y,
// vintage years to yr[], bottles to bot[]
Wine(const char * l, int y, const int yr[], const int bot[]);
// initialize label to l, number of years to y,
// create array objects of length y
Wine(const char * l, int y);

The Wine class should have a method GetBottles() that, given a Wine object with y years, prompts the user to enter the corresponding number of vintage years and bottle counts. A method Label() should return a reference to the wine name. A method sum() should return the total number of bottles in the second valarray<int> object in the Pair object.

The program should prompt the user to enter a wine name, the number of elements of the array, and the year and bottle count information for each array element. The program should use this data to construct a Wine object and then display the information stored in the object. For guidance, here’s a sample test program:

image

And here’s some sample output:

image

2. This exercise is the same as Programming Exercise 1, except that you should use private inheritance instead of containment. Again, a few typedefs might prove handy. Also, you might contemplate the meaning of statements such as the following:

PairArray::operator=(PairArray(ArrayInt(),ArrayInt()));
cout  << (const string &)(*this);

The class should work with the same test program as shown in Programming Exercise 1.

3. Define a QueueTp template. Test it by creating a queue of pointers-to-Worker (as defined in Listing 14.10) and using the queue in a program similar to that in Listing 14.12.

4. A Person class holds the first name and the last name of a person. In addition to its constructors, it has a Show() method that displays both names. A Gunslinger class derives virtually from the Person class. It has a Draw() member that returns a type double value representing a gunslinger’s draw time. The class also has an int member representing the number of notches on a gunslinger’s gun. Finally, it has a Show() function that displays all this information.

A PokerPlayer class derives virtually from the Person class. It has a Draw() member that returns a random number in the range 1 through 52, representing a card value. (Optionally, you could define a Card class with suit and face value members and use a Card return value for Draw().) The PokerPlayer class uses the Person show() function. The BadDude class derives publicly from the Gunslinger and PokerPlayer classes. It has a Gdraw() member that returns a bad dude’s draw time and a Cdraw() member that returns the next card drawn. It has an appropriate Show() function. Define all these classes and methods, along with any other necessary methods (such as methods for setting object values) and test them in a simple program similar to that in Listing 14.12.

5. Here are some class declarations:

image

image

image

Note that the class hierarchy uses MI with a virtual base class, so keep in mind the special rules for constructor initialization lists for that case. Also note the presence of some protected-access methods. This simplifies the code for some of the highfink methods. (Note, for example, that if highfink::ShowAll() simply calls fink::ShowAll() and manager::ShowAll(), it winds up calling abstr_emp::ShowAll() twice.) Provide the class method implementations and test the classes in a program. Here is a minimal test program:

image

Why is no assignment operator defined?

Why are ShowAll() and SetAll() virtual?

Why is abstr_emp a virtual base class?

Why does the highfink class have no data section?

Why is only one version of operator<<() needed?

What would happen if the end of the program were replaced with this code?

image

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

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