home account info subscribe login search My ITKnowledge FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
To access the contents, click the chapter and section titles.

C++ in Plain English
(Publisher: IDG Books Worldwide, Inc.)
Author(s): Brian Overland
ISBN: 1558284729
Publication Date: 04/01/96

Bookmark It

Search this book:
 
Previous Table of Contents Next


The definitions of the new CIoStr functions are simple.

#include <stdio.h>
#include “ciostr.h”

void CIoStr::output(void)
   printf(“%s”, get());
}
void CIoStr::input(void) {
   char buffer[256];

   gets(buffer);
   cpy((buffer);
}

Only two functions (input and output) are defined here; these functions are defined with the CIoStr:: prefix. Many other functions are inherited from the base class CStr, all of them were defined with the CStr:: prefix. For example:

void CStr::cpy(char *s) { // Copy string arg.
   int n;

   n = strlen(s);
   if (nLength <> n) {
       if (pData)
             free(pData);
       pData = (char*) malloc(n + 1);
       nLength = n;
   }
   strcpy(pData, s);
}

So CIoStr supports two kinds of functions: those contributed by the base class, CStr, and those added by CIoStr itself. The prefix varies depending on where the member function is declared and defined—in the base class or in the derived class.

Overriding Functions and Clarifying Scope

You’ve already seen inheritance at work, whether or not you’ve noticed it. The definitions of the CIoStr functions use functions from the base class. Take another look at the input function:

void CIoStr::input(void) {
   char buffer[256];

   gets(buffer);
   cpy(buffer);
}

The last statement in the definition calls the CStr::cpy function. As in other member-function definitions, the function can be referred to as cpy, because it is a member of CIoStr (by virtue of inheritance from CStr).

The situation gets more complex, though, if the derived class, CIoStr, overrides one or more functions in the base class. In this example, there is little motivation to override any of the CStr functions, but it is still perfectly valid. For example:

class CIoStr : public CStr {
public:
   void input(void);
   void output(void);

// Overridden from base class CStr
   void cpy(char *s);
};

void CIoStr::cpy(char *s) {
  // Alternative implementation of cpy function
}

Now there are two versions of cpy: one defined in the CIoStr class and a second one defined in its base class, CStr. Member functions of CIoStr can call either of these functions. But now, to call the original version of cpy, you must use the scope operator (::) to specify that the CStr version should be called. Otherwise, the new version (CIoStr::cpy) is assumed.

void CIoStr::input(void) {
   char buffer[256];

   gets(buffer);
   CStr::cpy(buffer); // Call base-class version
                        // of cpy.
}

In general, when the compiler sees a name, it attempts to resolve the scope in the following order:

  If the context is within a member-function definition, the compiler checks whether the name is declared within the same class. If it is, the compiler uses that version.
  Next, the compiler checks the declaration of the base class. (This is done recursively, so if the base class has its own base class, all the base classes are checked.)
  Finally, if the name is not declared within the class hierarchy, the compiler checks to see whether it is declared globally.
If a member function is intended to be overridden by derived classes, then the function should usually be declared virtual, for reasons explained at the end of the chapter. However, you can ignore virtual functions for now.

Inheritance Hierarchies

As the last section hinted, base classes can have their own base classes, and derived classes can have derived classes.

This arrangement may sound complex, so let’s take a simple example. Suppose you want to declare a class that has even more functions available than CIoStr has. You can derive a class from CIoStr even though CIoStr itself was derived from CStr.

#include “ciostr.h”

class CFontIoStr : public CIoStr {
   int ptsize;
public:
   void clr(void) { cpy(“ ”); }
   void setchar(int c, int n);
   void set-font(int size);
};

This new class, CFontIoStr, inherits all the members of CStr and CIoStr, because each successive generation adds or overrides members from the previous class. Nothing is ever lost (although access rights can sometimes change, as explained later in this chapter). CFontIoStr has one new data member (ptsize) and three new member functions that the other classes do not have.

The three classes—CStr, CIoStr, and CFontIoStr—create a class hierarchy, albeit a simple one. The original string class, CStr, is indirectly a base class of CFontIoStr, the most recent class. Through inheritance, CStr passes its contents all the way down to CFontIoStr, much as grandparents pass genes to a grandchild. Figure 8.1 illustrates this simple class hierarchy.

Much more complex class hierarchies are possible. You can derive several classes from any given base class, and the result can be a family tree of classes as elaborate as you like.


Figure 8.1  A class hierarchy.

Without Inheritance: Doing it the Hard Way

Although inheritance is not the only technique for creating reusable software (nor is it automatically the best technique in all cases), it’s useful to see how inheritance can save a good deal of work in many cases.

Suppose you don’t have access to the original CStr source code except for the class declaration. (Consequently, you don’t have access to source code for function definitions.) But you want to write a new class, CIoStr, with all the capabilities of CStr plus a couple more. How would you create a CIoStr class without using inheritance and without rewriting all the CStr functions from scratch?

You could write such a class without inheritance, but you would have to do the following: write a new class that forms, in effect, a shell around a CStr object; then translate calls to the outer class into calls to the CStr object. Figure 8.2 illustrates this scheme conceptually.


Figure 8.2  Object containment, simulating inheritance.

The difficulty of this approach is that every call to a CStr function must be explicitly translated into a call to the CStr object. Here’s what the code would look like:

#include “cstr.h”

class CIoStr {
   CStr  str;
public:
// New functions
   void input(void);
   void output(void);

// Old functions, simulating CStr inheritance
   char *get(void) const {return str.get(); }
   int getlength(void) const
      {return str.getlength();}
   void cpy(char *s) {str.cpy(s);}
   void cat(char *s) {str.cat(s);}
...


Previous Table of Contents Next
[an error occurred while processing this directive]

Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.