Section 2 - Derived Classes, Function Chaining and Constructors
4A.2.1 A Derived Class
If we wanted a phone-with-extension, then we might consider pasting all of the above code into a new file and then adding one more member, the extension. Then we add whatever member functions are needed to handle this extension. In C++, we don't have to do that.
We can derive a class from Phone, called PhoneWX like so:
// Derived Class PhoneWX Prototype ----------------------------- class PhoneWX : public Phone { // additional member string extension; public: PhoneWX(string ac = "000", string num = "0000000", string ex = "00"); bool setPhoneWX(string ac = "000", string num = "0000000", string ex = "00"); string getExtension(); bool setExtension(string num); string toString(); void showPhone(); };
PhoneWX is called a derived class or sub-class of Phone. This class prototype tells us that we are creating a new class that contains everything of the base class, plus whatever else we put in. We see that we are adding
- one new data member, the extension, and
- six new member functions.
Simply put, if we declare an object of the base class, Phone, it only contains what a Phone should - the area_code and number. If we declare an object of the derived class, PhoneWX, it contains the area_code , number plus the extension.
A derived class must possess an is a relationship with its base class. An Employee is a Person. A Truck is an Automobile. A PhoneWX is a Phone. However, a UserInterfaceForEmployee is NOT an Employee, and an EnginePart is NOT an Automobile. Making one class a sub-class of another just because, in your own mind, you can program something more easily is wrong headed. You will lose points. So make sure every extension of a base class to a derived class bears an "is a" relationship. (-5 points)
4A.2.2 Method Overriding
Some methods have the same name in base and derived classes, like toString(). If toString() is called through a base class object, then the base class version is executed. If called through a derived class object, the derived version gets executed.
Phone base("800", "1234567"); PhoneWX sub("313", "5551234", "39"); cout << base.toString() << endl; cout << sub.toString() << endl;
This is called function or method overriding. When you create a method in your derived class having the same name as one in your base class, and taking the same parameters, you are overriding the base class method. Any object of the derived class will use the derived method, not the base method. As you see above sub.toString() calls the PhoneWX version of toString().
4A.2.3 Added Member Functions
There are also new member functions in the derived class that have names which are not part of the base class. These are new, added methods, and in the above example a few such methods are getExtension(), setExtension() and the PhoneWX() constructor. In these cases, there is no ambiguity. Only a sub-class object (an object of the derived class) can reference these methods. A base class object doesn't possess these methods.
Here is a new main() that demonstrates the above principles, followed by the complete definitions of the derived class methods.
// main method --------------------------------------- int main() { PhoneWX her; her.setPhoneWX("415", "5551234", "99"); her.showPhone(); cout << "Separately: \n" << her.getAreaCode() << " " << her.getNumber() << " " << her.getExtension() << "\n"; return 0; } // PhoneWX method definitions -------------------------- PhoneWX::PhoneWX(string ac, string num, string ext) { if ( !setPhoneWX(ac, num, ext) ) setPhoneWX("000", "0000000", "00"); } bool PhoneWX::setPhoneWX(string ac, string num, string ext) { // first base class stuff if ( !setPhone(ac, num) ) return false; // don't change anything, return false // now the extension if ( !setExtension(ext) ) return false; // don't change anything, return false return true; // if ac/num were good, but ext was bad, we have a little weirdness - // ac/num get changed, but ext doesn't. I'll leave this weakness as // an exercise for the reader to fix. } string PhoneWX::getExtension() { return extension; } bool PhoneWX::setExtension(string ext) { if (ext.length() == 2 && isNumber(ext) ) { extension = ext; return true; } return false; } string PhoneWX::toString() { return "ext. " + extension; } void PhoneWX::showPhone() { cout << Phone::toString() << " " << toString() << endl; } // end PhoneWX method definitions ---------------------
Here is an output from this main():
4A.2.4 Invoking Base Class Methods from Derived Class Definitions
From the point of view of the derived class, when it looks "up" at the base class, the base methods fall into two categories:
- Overridden methods - methods that have the same name as their counterparts in the derived class.
- Unique methods - methods that are not overridden by the derived class.
If a derived class member function wanted to call a unique base class method, the derived class function would do so as if both it and the base class method were members of the same class (which they are!). We are utilizing this property in the setPhone() call, below:
bool PhoneWX::setPhoneWX(string ac, string num, string ext) { // first base class stuff if ( !setPhone(ac, num) ) return false; // don't change anything, return false // now the extension if ( !setExtension(ext) ) return false; // don't change anything, return false return true; }
setPhone() is being called from the derived setPhoneWX(). No problem here. We let the base class take care of itself, and we go on to deal with the extra stuff ourselves.
Also, notice that, as usual when calling one instance method from another instance method of the same class, we do not use an object to dereference the method. The method call, setPhone(), is assumed to be de-referenced through the implied this. this->setPhone() is another way to invoke the method. That's because the object that did the calling (*this), is at the same time a Phone object and a PhoneWX object. There is only one object (just like you are both a person and a student).
Scope Resolution Operator ::
However, if a derived class method wants to call a base class method that is overridden in the derived class, there is a potential problem. For example, some methods in the derived class want to access the toString() method of the base class. If they call toString() they will get the derived class version that overrides the base class version. How, then, can the derived class methods call the base class version of toString()? Answer: Through use of the scope resolution operator, as in baseclass::methodname.
Look at showPhone() to see how this is done:
void PhoneWX::showPhone() { cout << Phone::toString() << " " << toString() << endl; }
Notice how the first toString() invocation is prefaced with the base class name Phone::, and the second toString() invocation has no prefix, meaning it refers to a method of the same class we are defining, namely, PhoneWX. This is called method- or function-chaining. In non-constructor methods, if the derived class method calls an overridden base class method (which implies it has the same signature), then it uses this kind of function chaining to do so.
4A.2.5 Private and Protected
For ease of reference if you return to these lessons, I want to repeat something that was stated in the last module section, but give it a heading of its own: the difference between Protected and Private data. We just said that a derived class method cannot access the private data of its base class. That means that PhoneWX methods cannot use area_code or number directly. However, any member of the base class that is of the protected category is accessible to the derived class member functions. This is the reason we create some data as protected in a base class. Other methods that are not "family", such as the client method main(), cannot get at protected data. Only methods in derived classes have this privilege.
We are making use of this in the protected base class method isNumber(). PhoneWX methods can call this base class function because PhoneWX is derived from Phone, where isNumber() is classified as protected. However we could not call isNumber() from main() or other methods that are not in the derived class.
4A.2.6 Pointers to Base and Derived Objects
There is a universal rule in C++ about base class pointers and derived class pointers. It goes like this:
Fundamental Law of Base/Derived PointersA base class pointer can point to a derived class object without being cast (coerced). A derived class pointer can only point to a base class object if a type cast is used.
Here is an illustration of this rule:
Phone *phPtr, myPhone; PhoneWX *pwxPtr, myPwx; phPtr = &myPwx; // ok phPtr = pwxPtr; // ok pwxPtr = &myPhone; // compiler error pwxPtr = phPtr; // compiler error // or phPtr = new PhoneWX; // ok pwxPtr = new Phone; // compiler error // if casts are used, we can overcome above limitation, e.g.: pwxPtr = (PhoneWX *)phPtr;
This will come in to play for us later on in this course.
4A.2.7 Constructors in Inheritance
When deriving a sub class from a base class, the derived class constructor will always call a base class constructor, either implicitly or explicitly, depending on your code. Let's assume the following base class constructors and derived class prototype:
#include <iostream> #include <string> using namespace std; // Classes -------------------------------- class Base { public: Base() { cout << "base 1\n"; } Base(int x) { cout << "base 2\n"; } }; class Sub : public Base { public: Sub(int x); };
If a derived class constructor does not explicitly call a base class constructor, then the base class default constructor will be executed first, automatically, before the derived constructor function body.
// default Base constructor will get called implicitly. Sub::Sub(int x) { cout << "sub\n"; } // main method --------------------------------------- int main() { Sub s(99); return 0; } /* --- First Run ------------------- base 1 sub Press any key to continue . . . ----------------------------------- */
If, from your derived class constructor, you want to explicitly call a particular base class constructor, use special notation:
// alternate Base constructor is being called explicitly, bypassing the default constructor. Sub::Sub(int x) : Base(x) { cout << "sub\n"; } // main method --------------------------------------- int main() { Sub s(99); return 0; } /* --- Third Run ------------------- base 2 sub Press any key to continue . . . ----------------------------------- */
This is called constructor chaining.
You don't have to chain to the constructor of the base class that has the same signature as derived constructor in question. For example, this is constructor chaining, too:
// chaining to Base constructor that // takes a pram, even tough Sub() does not Sub::Sub() : Base(3) { cout << "sub\n"; }
Notice that the terminology chaining is slightly more flexible for constructors (which can chain to different signature constructors) than for non-constructor methods (which only chain to their same-signature base-class partners).
Warning - An Ill-Advised Option for Chaining Constructors
Do not use the, seemingly, equivalent notation this->base( params ) from inside your derived constructor body. This will result in two potential problems:
- It will result in two base constructor calls, default followed by the parameter call, before the derived method body.
- It will destroy any virtual function capability in some compilers. We will learn about virtual functions in a future lesson.
Here is an example of an ill-advised attempt to chain constructors. Don't do it this way:
// alternate Base constructor is being called explicitly, but default constructor still // called first, implicitly. Sub::Sub(int x) { this->Base::Base(x); // don't do this. cout << "sub\n"; } // main method --------------------------------------- int main() { Sub s(99); return 0; } /* --- Second Run ------------------- base 1 base 2 sub Press any key to continue . . . ----------------------------------- */