In this particular case, the class needs one more data member to hold the ratings value. It should also have a method for retrieving the rating and a method for resetting the rating. So the class declaration could look like this:
// simple derived class
class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; // add a data member
public:
RatedPlayer (unsigned int r = 0, const string & fn = "none",
const string & ln = "none", bool ht = false);
RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
unsigned int Rating() const { return rating; } // add a method
void ResetRating (unsigned int r) {rating = r;} // add a method
};
The constructors have to provide data for the new members, if any, and for the inherited members. The first RatedPlayer constructor uses a separate formal parameter for each member, and the second RatedPlayer constructor uses a TableTennisPlayer parameter, which bundles three items (firstname, lastname, and hasTable) into a single unit.
Constructors: Access Considerations
A derived class does not have direct access to the private members of the base class; it has to work through the base-class methods. For example, the RatedPlayer constructors cannot directly set the inherited members (firstname, lastname, and hasTable). Instead, they have to use public base-class methods to access private base-class members. In particular, the derived-class constructors have to use the base-class constructors.
When a program constructs a derived-class object, it first constructs the base-class object. Conceptually, that means the base-class object should be constructed before the program enters the body of the derived-class constructor. C++ uses the member initializer list syntax to accomplish this. Here, for instance, is the code for the first RatedPlayer constructor:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
rating = r;
}
The following part is the member initializer list:
: TableTennisPlayer(fn, ln, ht)
It’s executable code, and it calls the TableTennisPlayer constructor. Suppose, for example, a program has the following declaration:
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
The RatedPlayer constructor assigns the actual arguments "Mallory", "Duck", and true to the formal parameters fn, ln, and ht. It then passes these parameters on as actual arguments to the TableTennisPlayer constructor. This constructor, in turn, creates the embedded TableTennisPlayer object and stores the data "Mallory", "Duck", and true in it. Then the program enters the body of the RatedPlayer constructor, completes the construction of the RatedPlayer object, and assigns the value of the parameter r (that is, 1140) to the rating member (see Figure 13.2 for another example).
Figure 13.2. Passing arguments through to a base-class constructor.
What if you omit the member initializer list?
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) // what if no initializer list?
{
rating = r;
}
The base-class object must be created first, so if you omit calling a base-class constructor, the program uses the default base-class constructor. Therefore, the previous code is the same as the following:
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) // : TableTennisPlayer()
{
rating = r;
}
Unless you want the default constructor to be used, you should explicitly provide the correct base-class constructor call.
Now let’s look at code for the second constructor:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
: TableTennisPlayer(tp)
{
rating = r;
}
Again, the TableTennisPlayer information is passed on to a TableTennisPlayer constructor:
TableTennisPlayer(tp)
Because tp is type const TableTennisPlayer &, this call invokes the base-class copy constructor. The base class didn’t define a copy constructor, but recall from Chapter 12 that the compiler automatically generates a copy constructor if one is needed and you haven’t defined one already. In this case, the implicit copy constructor, which does memberwise copying, is fine because the class doesn’t directly use dynamic memory allocation. (The string members do use dynamic memory allocation, but, recall, memberwise copying will use the string class copy constructors to copy the string members.)
You may, if you like, also use member initializer list syntax for members of the derived class. In this case, you use the member name instead of the class name in the list. Thus, the second constructor can also be written in this manner:
// alternative version
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
: TableTennisPlayer(tp), rating(r)
{
}
These are the key points about constructors for derived classes:
• The base-class object is constructed first.