Conversations with Computer Scientists

Wednesday, September 7, 2016

Inheritance and Base Class Constructors in C++

(An update of a post made on Jan 16, 2013.)

In the 7th edition of Walls & Mirrors: C++, C++ Interlude 1 describes three types of boxes for a video game:

Plain box—a plain old box that holds only one item.
Toy box—a box that has color and holds only one item.
Magic box—a box that holds only one item, but magically changes it to the first item that was ever stored in the box.

The three classes of boxes are related by inheritance: PlainBox is the base class, and both ToyBox and MagicBox are derived from PlainBox. For simplicity, let’s just focus on the classes PlainBox and ToyBox.

Suppose that we define PlainBox as in Listing C1-3:

/** @file PlainBox.h */
#ifndef PLAIN_BOX_
#define PLAIN_BOX_
#include "BoxInterface.h"

template<class ItemType>
class PlainBox
{
private:
   ItemType item;
    
public:
   PlainBox();
   PlainBox(const ItemType& theItem);
   virtual void setItem(const ItemType& theItem);
   virtual ItemType getItem() const;
};

#include "PlainBox.cpp"
#endif

This class has a simple implementation, as you can see next and in Listing C1-4:

/** @file PlainBox.cpp */

template<class ItemType>
PlainBox::PlainBox()
{
   std::cout << "PlainBox() called" << std::endl;
} // end default constructor

template<class ItemType>
PlainBox::PlainBox(const ItemType& theItem) : item(theItem)
{
   std::cout << "PlainBox(theItem) called" << std::endl;
} // end constructor

template<class ItemType>
void PlainBox::setItem(const ItemType& theItem)
{
   item = theItem;
} // end setItem

template<class ItemType>
ItemType PlainBox::getItem() const
{
   return item;
} // end getItem

We included cout statements in the constructors so we could tell when they are called.

Now let’s derive the class ToyBox from PlainBox. An instance of ToyBox is an enhanced PlainBox object that has a color. We can write the header file for ToyBox as in Listing C1-5:

/* @file ToyBox.h */
#ifndef TOYBOX_
#define TOYBOX_
#include "PlainBox.h"

enum Color {BLACK, RED, BLUE, GREEN, YELLOW, WHITE};

template<class ItemType>
class ToyBox : public PlainBox
{
private:
   Color boxColor;
public:
   ToyBox();
   ToyBox(const Color& theColor);
   ToyBox(const ItemType& theItem, const Color& theColor);
   Color getColor() const;
}; // end ToyBox

#include "ToyBox.cpp"
#endif

Although we could provide the class with a setColor method, we choose to prevent the client from altering the color of these boxes. In particular, a ToyBox object created by the default constructor will have a default color that cannot be changed.

The constructors of ToyBox are the focus of our discussion here, so let’s review how the constructors of a derived class relate to the constructors of its base class in C++:

The derived class does not inherit the base class constructors.
Each derived class constructor can, and should, call a base class constructor.
If a derived class constructor does not explicitly call a base class constructor, C++ will call the base class default constructor for you. 

For example, the definition of ToyBox’s default constructor in Listing C1-6 is

template<class ItemType>
ToyBox::ToyBox() : boxColor(BLACK)
{
// end default constructor

This constructor implicitly calls PlainBox’s default constructor as its first step before giving a value to boxColor. The same is true of ToyBox’s second constructor:

template<class ItemType>
ToyBox::ToyBox(const Color& theColor) : boxColor(theColor) 
{
// end constructor

For either of these two constructors, we could explicitly call PlainBox’s default constructor by using an initializer as follows:

template<class ItemType>
ToyBox::ToyBox() : PlainBox(), boxColor(BLACK)
{
// end default constructor

template<class ItemType>
ToyBox::ToyBox(const Color& theColor) : PlainBox(), boxColor(theColor)
{
// end constructor

In this case,

If the C++ compiler detects an initializer call to a base class constructor, it does not add its own call to such a constructor.

There is another way for us to call a base class constructor that you should NOT use. For example, we could define ToyBox’s default constructor as

template<class ItemType>
ToyBox::ToyBox() : boxColor(BLACK)
{
   PlainBox();
// end default constructor

Although this will work, the compiler will insert its own call to PlainBox’s default constructor, because of our previous bulleted rule. We can restate this rule as

If the C++ compiler does not detect an initializer call to a base class constructor, it adds its own call to the base class’s default constructor.

If you create an instance of ToyBox using the previous constructor, you will see the result of two executions of the cout statement in PlainBox’s default constructor.

Finally, let’s define ToyBox’s third constructor, which sets both the item and color of a ToyBox object. We can call setItem to set the item:

template<class ItemType>
ToyBoxTry::ToyBoxTry(const ItemType& theItem, const Color& theColor): boxColor(theColor) 
{
   setItem(theItem);
// end constructor

Or we could use an initializer to call PlainBox’s second constructor:

template<class ItemType>
ToyBox::ToyBox(const ItemType& theItem, const Color& theColor)  
                : PlainBox(theItem), boxColor(theColor)
{
// end constructor

In the first case, PlainBox’s default constructor is called implicitly. In the second case, PlainBox’s second constructor is called explicitly by the initializer, so the compiler does not insert a call to PlainBox’s default constructor.

To summarize, we can define the implementation file for ToyBox, shown in Listing C1-6:

/* @file ToyBox.cpp */

#include “ToyBox.h”

template<class ItemType>
ToyBox::ToyBox() : boxColor(BLACK) 
{
   // Implicit call to PlainBox's default constructor
// end default constructor

template<class ItemType>
ToyBox::ToyBox(const Color& theColor) : boxColor(theColor)
{
   // Implicit call to PlainBox's default constructor
// end constructor

template<class ItemType>
ToyBox::ToyBox(const ItemType& theItem, const Color& theColor)
                : PlainBox(theItem), boxColor(theColor)
{
   // No call to PlainBox's default constructor
// end constructor

template<class ItemType>
Color ToyBox::getColor() const
{
   return boxColor;
// end getColor