Wrox Press C++ Tutorial


Inheritance in Classes

When one class is defined based on another class (or more generally, based on several others), the former is referred to as a derived class. A derived class automatically contains all the data members of the class or classes which are used to define it and, with some restrictions, the function members as well. The class is said to inherit the data members and function members of the classes on which it is based.

The only members of a base class which are not inherited by a derived class are the destructor, the constructors and any member functions overloading the assignment operator. All other function members, together with all the data members of a base class, will be inherited by a derived class.

What is a Base Class?

A base class is any class that is used in the definition of another class. For example, when a class B is defined directly in terms of a class A, A is said to be a direct base class of B. In the previous diagram, the class Crate was a direct base class of BeerCrate. When a class such as BeerCrate is defined in terms of another class Crate, BeerCrate is said to be derived from Crate. Because Crate is itself defined in terms of the class Box, Box is said to be an indirect base class of BeerCrate. We shall see how this is expressed in the class definition in a moment. The relationship between a derived class and a base class is illustrated in the following figure:

Deriving Classes from a Base Class

Let's go back to the original Box class with public data members that we had at the beginning of the last chapter:

// Listing 8_01-01
class Box
{
   public:
      double length;
      double breadth;
      double height;
      Box(double lv=1.0, double bv=1.0, double hv=1.0)
      {
         length = lv;
         breadth = bv;
         height = hv;
      }
};

We have included a constructor in the class so that we can initialize objects when we declare them. Suppose we now need another class CandyBox, which is the same as a Box object, but also has another data member - a pointer to a text string. We can define CandyBox as a derived class with the class Box as the base class, as follows:

// Listing 8_01-02
class CandyBox: Box
{
   public:
      char* contents;
      CandyBox(char* str= "Candy")            // Constructor
      {
         contents = new char[ strlen(str) + 1 ];
         strcpy(contents, str);
      }

      ~CandyBox()                             // Destructor
      { delete[] contents; };
};

The base class, Box, appears after the class name for the derived class CandyBox and is separated from it by a colon. In all other respects, it looks like a normal class definition. We have added the new member, contents, and, since it is a pointer to a string, we need a constructor to initialize it and a destructor to release the memory for the string. We have also put a default value for the string describing the contents of a CandyBox object in the constructor. Objects of the class CandyBox contain all the members of the base class, Box, plus the additional data member, contents.

Try It Out - Using a Derived Class

We can see how our derived class works in an example. Although we used the Wizard Bar to manage our classes in the previous chapter, we will hold off doing that for the most part in this chapter, because we are often combining fragments that we have been developing in discussion. However, if you feel comfortable using the Wizard Bar to put the examples together, it would be good experience to do it that way. Here is the code for our first example using a derived class:

// Ex8_01.cpp
// Using a derived class
#include <iostream>
#include <string>               // For strlen() and strcpy()
using namespace std;

// Insert Box definition (Listing 8_01-01)

// Insert CandyBox definition (Listing 8_01-02)

int main()
{
   Box myBox(4.0,3.0,2.0);           // Create Box object
   CandyBox myCandyBox;              // Create CandyBox objects
   CandyBox myMintBox("Wafer Thin Mints"); 

   cout << endl
        << "myBox occupies " 
        << sizeof myBox             // Show how much memory
        << " bytes" << endl         // the objects require
        << "myCandyBox occupies " << sizeof myCandyBox
        << " bytes" << endl
        << "myMintBox occupies " << sizeof myMintBox
        << " bytes";

   cout << endl
        << "myBox length is " << myBox.length;

   myBox.length = 10.0;
   // myCandyBox.length = 10.0;  // uncomment this for an error

   cout << endl;
   return 0;
}

How It Works

After declaring a Box object and two CandyBox objects, we output the number of bytes occupied by each object. Let's look at the output:

The first line is what we would expect from our discussion in the last chapter. A Box object has three data members of type double, each of which will be 8 bytes, making 24 bytes in all. Both our CandyBox objects are the same size: 32 bytes. The length of the string doesn't affect the size of an object, as the memory for it is allocated in the free store. The 32 bytes are made up of 24 bytes for the three double members inherited from the base class Box, plus 4 bytes for the pointer member contents, which makes 28 bytes... so where did the other 4 bytes come from? This is due to the compiler aligning members at addresses that are multiples of 8 bytes. You should be able to demonstrate this by adding an extra member of type int, say, to the class CandyBox. You will find that the size of a class object is still 32 bytes.

We also output the value of the length member of the Box object myBox. Even though we have no difficulty accessing this member of the Box object, if you uncomment the following statement in the function main(),

// myCandyBox.length = 10.0;  // uncomment this for an error

then the program will no longer compile. The compiler will generate a message,

error C2248: 'length': cannot access public member declared in class 'Box'

which means that the length member from the base class is not accessible. In the derived class, the member length has become private.

The reason for this is that there is a default access specifier of private for a base class - it's as if the first line of our class definition had been:

class CandyBox: private Box

There always has to be an access specification for a base class, which will determine the status of the inherited members in the derived class. Omitting an access specification when specifying a base class causes the compiler to assume that it's private. If we change the definition of the class CandyBox to the following,

class CandyBox: public Box
{
   // Contents the same as in Listing 8_01-02
};

then the member length in the derived class will be inherited as public and will be accessible in the function main(). With the access specifier public for the base class, all the inherited members originally specified as public in the base class will have the same access level in the derived class.


© 1998 Wrox Press