Week 13

Base-class Constructors

The running-man icon.

Now that you've learned about inheritance and inherited members, let's look at how derived-class constructors are written. We'll with our simple "finger-exercise" example that lets you concentrate on one piece of the inheritance puzzle at a time. You can re-open it directly from Replit, or you can click the Running Man to open my copy, and Fork it again.

A constructor must have the same name as the class, so, when you create a new class, it cannot inherit any of the base class constructors. Instead, it defines new ones.

To see how that works, modify the Person class, which now uses only the synthesized default constructor that is automatically written by your compiler, when you don't supply any explicit constructors. In person.h add this code:

class Person
{
public:
    Person();
    Person(const std::string& pname);
   . . .
};

In person.cpp add an implementation that prints a message so you can keep track of which constructor is called. The working constructor should use its string parameter to initialize the m_name data member in addition to printing a diagnostic message.

Person::Person() { cout << "Calling Person()" << endl;} 

Person::Person(const string& pname)
{
    cout << "Calling Person(" << pname << ")" << endl;
    name = pname;
}
Week 13

Derived-class Constructors

The derived Student class already has a constructor. Go ahead and modify it as well, so it prints a message like this:

Student::Student(const string sname, long sid)
{
    setName(sname);
    studentID = sid;  
    cout << "Calling Student(" << name << ", "
        << sid << ")\n";
}

Modify main inside client.cpp to create two objects, one Person and one Student, and to print out their info, just like the existing example.

Person pete("Pete the Pirate");
Student steve("Steve", 1007);
cout << "pete->" << pete.getName() << endl;
cout << "steve->" << steve.getName() << endl;

Type make run to compile and run the modified program. You'll see that instead of only two constructor calls, which you'd expect, both Person constructors have been called, along with the Student constructor, for a total of three, even though only two objects are created. Why? Running the sample program.

Week 13

Constructor Chaining

Before a derived class constructor can do any of its work, it must first initialize all of the base class data members. This must happen before the derived constructor ever runs. (If this sounds familiar, it should. It is the same reason that your class data members are already initialized before the first line of your constructor runs!)

You needn't do anything special to make this happen. When your constructor runs, if implicitly calls the default or no-argument constructor in the base class as its first line of code, and that constructor calls its base class constructor, and so on, all the way up to the first class in your hierarchy. This is called constructor chaining.

So, consider for a second, the Student class constructor in the previous section. Even though not explicit in the source code, the following commands are executed when the Student constructor is invoked (after memory for both the Person part of the Student and the Student part of the Student has been allocated).

  1. call the Person default constructor
  2. call the setName() inherited member function
  3. assign the sid parameter to the data member
  4. print the diagnostic message

That's why you saw three diagnostic messages when you only created two objects. When you created the Student object named steve, the Student constructor first called the default constructor for the Person class.

Week 13

Default Constructors

What happens if you remove the default constructor from the Person class? Comment it out and you'll see that the Student constructor stops working: Removing the Person() constructor.

As the error message shows, every time you extend a class, the superclass must:

  1. have an explicit default constructor like Person(), or
  2. have a synthesized default constructor which is automatically written if the base class has no explicit constructors, or
  3. the derived class (Student in this case) must explicitly call another constructor in the base class.

So, how exactly do you explicitly call a superclass constructor?

Week 13

Calling the Base Constructor

Just as you can initialize data members before the constructor runs, you can initialize your object's "base part" by calling the base class constructor in the initializer list.

When you do this, the Student constructor invokes the Person(String) constructor, instead of using the setName() member function, as you've done up until now. This is the normal way to write derived class constructors.

Student::Student(const string sname, long sid)
    : Person(sname)   // this initializes name
{
    // setName(sname); // Remove this!!!
    studentID = sid;  
    cout << "Calling Student(" << name << ", "
        << sid << ")\n";
}

Now, when you run the sample program, instead of implicitly calling the Person default constructor (which no longer exists), you explicitly chain to the Person(string) constructor to initialize the name data member.

Week 13

Protected Members

The member functions and data members which are not declared private in the base class are called inherited members. An object may use its inherited members without any further qualification, exactly as if they were defined inside the object's own class.

A base class may allow a derived class access to a data member by using the keyword protected instead of private. Protected members are half-way between public and private; the derived (child) classes can directly access them, but the general public cannot.

Child opening a refridgerator.

These access specifiers work the same way most of us manage our own households. My grandchildren are free to open my refrigerator, getting a glass of orange juice without asking me; you, on the other hand, would have to knock at the front door, and ask first. My refrigerator has something similar to protected access.

On the other hand, even my grandchildren aren't permitted to grab my credit cardand charge up a storm on the Internet; my credit card is private.

In general, avoid using protected access to grant derived classes access to data members. This unnecessarily exposes the implementation of the base class and prevents easy modification. Add some protected member functions instead.