Chapter 15: Extending Classes


Creating subclasses is central to object oriented programming. Chapter 3 showed the basic syntax for defining a subclass. This chapter will present in greater detail the various aspects of how to create subclasses and how to provide them with the needed functionality.

More specifically, this chapter talks about the constraints that must be observed by a subclass constructor in relation to the base class constructor for C++ and Java. We also discuss for C++ the special syntax of a subclass copy constructor, a subclass copy assignment operator; upcasting in operator overload definitions for a subclass; and destructors for derived classes. The chapter also presents the notion of a virtual function in C++; these are functions that exhibit polymorphic behavior. For both C++ and Java, this chapter goes into the concept of an abstract class, and for the case of Java, the concept of an interface. The last part of the chapter is devoted to case-study programs in C++ and Java that involve small but moderately complex class hierarchies.

15.1 PUBLIC DERIVATION OF A SUBCLASS IN C++

As already mentioned in Chapter 3, when we say

    class Y : public X { /* ..... */ }; 

we declare X to be a public base of Y. The other options would be to declare X to be either a protected or a private base of Y. We will get to those later in this chapter.

A public derivation of a subclass in C++ buys us two things:

  • An object of a derived-class type can at any time be treated as an object of base-class type without explicit type conversion, provided both are manipulated through pointers or references. Therefore, Y* can be substituted anywhere X* is needed. For the Animal example shown at the beginning of Chapter 3, the following is therefore legal:

          Cat* cpr = new Cat( ... );      FourLegged* fpr = cpr; 

    But suppose we say

          FourLegged* fpr = new FourLegged(....); 

    Now we cannot say

          Cat* cpr = fpr;       // WRONG 

    because not every FourLegged is a Cat. If so needed, we are allowed to say

          Cat* cpr = new Cat( ... );      FourLegged* fpr = cpr;      Cat* cpr_2 = static_cast<Cat*>( fpr ); 

    This works because the system can figure out at run time that Cat is the true type of the object to which fpr points.

  • With a public derivation, all of the public and protected data members and member functions of the base class become available in the derived class. It's as if they were declared in the derived class itself. Suppose we have

          class FourLegged {                   // BASE          double weight;          //...      public:          double getWeight();          //...      };      class Cat : public FourLegged {      // DERIVED          int numClaws;          //...      public:          void print() { cout << getWeight() << endl;                             //(A)      }; 

    We are able to use the getWeight() function in the derived class in line (A) as if this function were defined locally in the derived class directly.

Although public and protected data members and member functions of a base class are directly visible in a derived class, the names used for them can be used again in a derived class. Therefore, as we show below, a public print() member function in the base class does not prevent us from defining a print() function again in the derived class. What about the resulting name conflict?

Name conflicts of this nature are avoided by using the rule that the derived class definition of a name hides all base class definitions of the same name.[1] If the base class definition of a name (that is also defined in a derived class) is needed in the derived class, it can be accessed via the scope operator ‘::'.

Speaking of function names in particular, the derived-class definition of a function hides all base-class functions of the same name, even when their signatures are different from that of the derived-class function. Such hidden base-class function definitions can always be accessed in the derived class via the scope operator.

We show below a base class and a derived class, with both possessing a member function print(), the function being public in the base class. Notice how in line (C) of the derived class we use the scope operator to access the base class version of print():

     class FourLegged {                    // BASE         double weight;         //...     public:         void print() { cout << weight << endl; }     };     class Cat : public FourLegged {       // DERIVED         int numClaws;         //...     public:         int getNumClaws();         void print() {                                                              //(B)             FourLegged::print();                                                    //(C)             cout << getNumClaws() << endl;         }         //....     }; 

The derived class's print in line (B) invokes the base class's print in line (C) by calling FourLegged::print(). You'd end up with infinite recursion if you replaced FourLegged::print() by just print() in line (C), because then the print() function in the derived class would be calling itself.

Since the private members of a base class are not visible in a derived class, one would naturally expect to be able to use those identifiers in a derived class. That is indeed the case. In the following example, we use the same two identifiers for the private data members in the base class and in the derived class, although the meanings ascribed to them in the two cases are different.

 
//DerivedNameConflict.cc #include <iostream> #include <string> using namespace std; class User { //BASE string name; // given name int age; // actual age public: User( string nam, int yy) : name( nam ), age( yy ) {} string getName() { return name; } }; class StudentUser : public User { //DERIVED string name; // nickname used at school int age; // assumed age for partying public: StudentUser( string str1, int yy1, string str2, int yy2 ) : User( str1, yy1 ), name( str2 ), age( yy2 ) {} string getName() { return name; } }; int main() { StudentUser student( "maryjo", 19, "jojo", 21 ); cout << student.getName() << endl; // jojo (D) cout << student.User::getName() << endl; // maryjo (E) return 0; }

Note that when getName() is invoked on the derived-class object student in line (D) in main, it is the derived-class definition of this function that gets invoked. But when we make the following call in line (E) of main:

    student.User::getName() 

it is the base-class definition of getName() that gets invoked.

[1]The process of determining as to which definition to use for a name is called name lookup. According to the C++ standard [41, clause 10.2], name lookup takes place before access control.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net