Professional C__ - Marc Gregoire [128]
The Superclass Method Has Default Arguments
Subclasses and superclasses can each have different default arguments, but the argument that is used depends on the declared type of the variable, not the underlying object. Following is a simple example of a subclass that provides a different default argument in an overridden method:
class Super
{
public:
virtual void go(int i = 2) {
cout << "Super's go with i=" << i << endl; }
};
class Sub : public Super
{
public:
virtual void go(int i = 7) {
cout << "Sub's go with i=" << i << endl; }
};
If go() is called on a Sub object, Sub’s version of go() will be executed with the default argument of 7. If go() is called on a Super object, Super’s version of go() will be executed with the default argument of 2. However (this is the weird part), if go() is called on a Super pointer or Super reference that really points to a Sub object, Sub’s version of go() will be called but it will use the default Super argument of 2. This behavior is shown in the following example:
Super mySuper;
Sub mySub;
Super& mySuperReferenceToSub = mySub;
mySuper.go();
mySub.go();
mySuperReferenceToSub.go();
The output of this code is as follows:
Super's go with i=2
Sub's go with i=7
Sub's go with i=2
The reason for this behavior is that C++ binds default arguments to the type of the expression describing the object being involved, not by the actual object type. For this same reason, default arguments are not “inherited” in C++. If the Sub class above failed to provide a default argument as its parent did, it would be overloading the go() method with a new non zero-argument version.
When overriding a method that has a default argument, you should provide a default argument as well, and it should probably be the same value. It is recommended to use a symbolic constant for default values so that the same symbolic constant can be used in subclasses.
The Superclass Method Has a Different Access Level
There are two ways you may wish to change the access level of a method — you could try to make it more restrictive or less restrictive. Neither case makes much sense in C++, but there are a few legitimate reasons for attempting to do so.
To enforce tighter restriction on a method (or on a data member for that matter), there are two approaches you can take. One way is to change the access specifier for the entire base class. This approach is described later in this chapter. The other approach is simply to redefine the access in the subclass, as illustrated in the Shy class that follows:
class Gregarious
{
public:
virtual void talk() { cout << "Gregarious says hi!" << endl; }
};
class Shy : public Gregarious
{
protected:
virtual void talk() { cout << "Shy reluctantly says hello." << endl; }
};
The protected version of talk() in the Shy class properly overrides the method. Any client code that attempts to call talk() on a Shy object will get a compile error:
myShy.talk(); // BUG! Attempt to access protected method.
However, the method is not fully protected. One only has to obtain a Gregarious reference or pointer to access the method that you thought was protected:
Shy myShy;
Gregarious& ref = myShy;
ref.talk();
The output of the preceding code is:
Shy reluctantly says hello.
This proves that making the method protected in the subclass did actually override the method (because the subclass version was correctly called), but it also proves that the protected access can’t be fully enforced if the superclass makes it public.
There is no reasonable way (or good reason why) to restrict access to a public parent method.
It’s much easier (and makes a lot more sense) to lessen access restrictions in subclasses. The simplest way is simply to provide a public method that calls a protected method from the superclass, as shown here:
class Secret
{
protected:
virtual void dontTell() { cout << "I'll never tell." << endl; }
};
class Blabber : public Secret
{
public:
virtual void tell() { dontTell(); }
};
A client