When you design a base class, you have to decide whether to make class methods virtual. If you want a derived class to be able to redefine a method, you define the method as virtual in the base class. This enables late, or dynamic, binding. If you don’t want the method to be redefined, you don’t make it virtual. This doesn’t prevent someone from redefining the method, but it should be interpreted as meaning that you don’t want it redefined.
Note that inappropriate code can circumvent dynamic binding. Consider, for example, the following two functions:
void show(const Brass & rba)
{
rba.ViewAcct();
cout << endl;
}
void inadequate(Brass ba)
{
ba.ViewAcct();
cout << endl;
}
The first function passes an object by reference, and the second passes an object by value.
Now suppose you use each with a derived class argument:
BrassPlus buzz("Buzz Parsec", 00001111, 4300);
show(buzz);
inadequate(buzz);
The show() function call results in the rba argument being a reference to the BrassPlus object buzz, so rba.ViewAcct() is interpreted as the BrassPlus version, as it should be. But in the inadequate() function, which passes an object by value, ba is a Brass object constructed by the Brass(const Brass &) constructor. (Automatic upcasting allows the constructor argument to refer to a BrassPlus object.) Thus, in inadequate(), ba.ViewAcct() is the Brass version, so only the Brass component of buzz is displayed.
Destructor Considerations
As mentioned earlier, a base class destructor should be virtual. That way, when you delete a derived object via a base-class pointer or reference to the object, the program uses the derived-class destructor followed by the base-class destructor rather than using only the base-class destructor.
Friend Considerations
Because a friend function is not actually a class member, it’s not inherited. However, you might still want a friend to a derived class to use a friend to the base class. The way to accomplish this is to type cast a derived-class reference or pointer to the base-class equivalent and to then use the type cast reference or pointer to invoke the base-class friend:
ostream & operator<<(ostream & os, const hasDMA & hs)
{
// type cast to match operator<<(ostream & , const baseDMA &)
os << (const baseDMA &) hs;
os << "Style: " << hs.style << endl;
return os;
}
You can also use the dynamic_cast<> operator, discussed in Chapter 15, “Friends, Exceptions, and More,” for the type cast:
os << dynamic_cast
For reasons discussed in Chapter 15, this would be the preferred form of type cast.
Observations on Using Base-Class Methods
Publicly derived objects can use base-class methods in many ways:
• A derived object automatically uses inherited base-class methods if the derived class hasn’t redefined the method.
• A derived-class destructor automatically invokes the base-class constructor.
• A derived-class constructor automatically invokes the base-class default constructor if you don’t specify another constructor in a member-initialization list.
• A derived-class constructor explicitly invokes the base-class constructor specified in a member-initialization list.
• Derived-class methods can use the scope-resolution operator to invoke public and protected base-class methods.
• Friends to a derived class can type cast a derived-class reference or pointer to a base-class reference or pointer and then use that reference or pointer to invoke a friend to the base class.
Class Function Summary
C++ class functions come in many variations. Some can be inherited, and some can’t. Some operator functions can be either member functions or friends, and others can only be member functions. Table 13.1, based on a similar table from
Table 13.1. Member Function Properties
Summary