Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Initializing Base and Derived Classes
Pass arguments from derived constructors to base class constructors.
Constructors and Initialization of Derived Classes
In the past two lessons, we've explored inheritance basics in C++ and the order in which derived classes are initialized. In this lesson, we'll examine more closely the role of constructors in the initialization of derived classes. To illustrate this, we'll continue using the simple Entity and Enemy classes:
class Entity
{
public:
int m_health{};
Entity(int health = 100)
: m_health{ health }
{
}
int getHealth() const { return m_health; }
};
class Enemy: public Entity
{
public:
int m_damage{};
Enemy(int damage = 10)
: m_damage{ damage }
{
}
int getDamage() const { return m_damage; }
};
With non-derived classes, constructors only need to worry about their own members. For example, consider Entity. We can create an Entity object like this:
int main()
{
Entity entity{ 100 }; // use Entity(int) constructor
return 0;
}
Here's what actually happens when entity is instantiated:
- Memory for entity is allocated
- The appropriate Entity constructor is called
- The member initializer list initializes variables
- The body of the constructor executes
- Control is returned to the caller
This is straightforward. With derived classes, things are slightly more involved:
int main()
{
Enemy enemy{ 25 }; // use Enemy(int) constructor
return 0;
}
Here's what actually happens when enemy is instantiated:
- Memory for enemy is allocated (enough for both the Entity and Enemy portions)
- The appropriate Enemy constructor is called
- The Entity object is constructed first using the appropriate Entity constructor. If no base constructor is specified, the default constructor will be used.
- The member initializer list initializes variables
- The body of the constructor executes
- Control is returned to the caller
The key difference between this case and the non-inherited case is that before the Enemy constructor can do anything substantial, the Entity constructor is called first. The Entity constructor sets up the Entity portion of the object, control returns to the Enemy constructor, and the Enemy constructor is allowed to finish its work.
Initializing base class members
One current limitation of our Enemy class is that there's no way to initialize m_health when we create an Enemy object. What if we want to set both m_damage (from the Enemy portion of the object) and m_health (from the Entity portion of the object) when we create an Enemy object?
New programmers often attempt to solve this problem as follows:
class Enemy: public Entity
{
public:
int m_damage{};
Enemy(int damage = 10, int health = 100)
// doesn't work
: m_damage{ damage }
, m_health{ health }
{
}
int getDamage() const { return m_damage; }
};
This is a good attempt and nearly the right approach. We definitely need to add another parameter to our constructor, otherwise C++ has no way of knowing what value we want to initialize m_health to.
However, C++ prevents classes from initializing inherited member variables in the member initializer list of a constructor. In other words, the value of a member variable can only be set in a member initializer list of a constructor belonging to the same class as the variable.
Why does C++ enforce this? The answer relates to const and reference variables. Consider what would happen if m_health were const. Because const variables must be initialized with a value at creation time, the base class constructor must set its value when the variable is created. However, when the base class constructor finishes, the derived class constructor's member initializer lists are executed. Each derived class would then have the opportunity to initialize that variable, potentially changing its value! By restricting initialization of variables to the constructor of the class those variables belong to, C++ ensures that all variables are initialized only once.
The end result is that the above example doesn't work because m_health was inherited from Entity, and only non-inherited variables can be initialized in the member initializer list.
However, inherited variables can still have their values changed in the body of the constructor using assignment. Consequently, new programmers often also try this:
class Enemy: public Entity
{
public:
int m_damage{};
Enemy(int damage = 10, int health = 100)
: m_damage{ damage }
{
m_health = health;
}
int getDamage() const { return m_damage; }
};
While this actually works in this case, it wouldn't work if m_health were const or a reference (because const values and references must be initialized in the member initializer list of the constructor). It's also inefficient because m_health gets assigned a value twice: once in the member initializer list of the Entity class constructor, and then again in the body of the Enemy class constructor. And finally, what if the Entity class needed access to this value during construction? It has no way to access it, since it's not set until the Enemy constructor executes (which happens last).
So how do we properly initialize m_health when creating an Enemy class object?
In all of the examples so far, when we instantiate an Enemy class object, the Entity class portion has been created using the default Entity constructor. Why does it always use the default Entity constructor? Because we never told it to do otherwise!
Fortunately, C++ gives us the ability to explicitly choose which Entity class constructor will be called! To do this, simply add a call to the Entity class constructor in the member initializer list of the derived class:
class Enemy: public Entity
{
public:
int m_damage{};
Enemy(int damage = 10, int health = 100)
: Entity{ health } // Call Entity(int) constructor with value health!
, m_damage{ damage }
{
}
int getDamage() const { return m_damage; }
};
Now, when we execute this code:
#include <iostream>
int main()
{
Enemy goblin{ 15, 50 }; // use Enemy(int, int) constructor
std::cout << "Health: " << goblin.getHealth() << '\n';
std::cout << "Damage: " << goblin.getDamage() << '\n';
return 0;
}
The base class constructor Entity(int) will be used to initialize m_health to 50, and the derived class constructor will be used to initialize m_damage to 15!
Thus, the program will print:
Health: 50 Damage: 15
In more detail, here's what happens:
- Memory for goblin is allocated.
- The Enemy(int, int) constructor is called, where damage = 15, and health = 50.
- The compiler looks to see if we've asked for a particular Entity class constructor. We have! So it calls Entity(int) with health = 50.
- The base class constructor member initializer list sets m_health to 50.
- The base class constructor body executes, which does nothing.
- The base class constructor returns.
- The derived class constructor member initializer list sets m_damage to 15.
- The derived class constructor body executes, which does nothing.
- The derived class constructor returns.
This may seem somewhat intricate, but it's actually very simple. All that's happening is that the Enemy constructor is calling a specific Entity constructor to initialize the Entity portion of the object. Because m_health lives in the Entity portion of the object, the Entity constructor is the only constructor that can initialize that value.
Note that it doesn't matter where in the Enemy constructor member initializer list the Entity constructor is called -- it will always execute first.
Now we can make our members private
Now that you know how to initialize base class members, there's no need to keep our member variables public. We can make our member variables private again, as they should be.
As a quick refresher, public members can be accessed by anybody. Private members can only be accessed by member functions of the same class. Note that this means derived classes cannot access private members of the base class directly! Derived classes will need to use access functions to access private members of the base class.
Consider:
#include <iostream>
class Entity
{
private: // our member is now private
int m_health{};
public:
Entity(int health = 100)
: m_health{ health }
{
}
int getHealth() const { return m_health; }
};
class Enemy: public Entity
{
private: // our member is now private
int m_damage;
public:
Enemy(int damage = 10, int health = 100)
: Entity{ health } // Call Entity(int) constructor with value health!
, m_damage{ damage }
{
}
int getDamage() const { return m_damage; }
};
int main()
{
Enemy goblin{ 15, 50 }; // use Enemy(int, int) constructor
std::cout << "Health: " << goblin.getHealth() << '\n';
std::cout << "Damage: " << goblin.getDamage() << '\n';
return 0;
}
In the above code, we made m_health and m_damage private. This is fine, since we use the relevant constructors to initialize them, and use public accessors to get the values.
This prints, as expected:
Health: 50 Damage: 15
We'll talk more about access specifiers in the next lesson.
Another example
Let's examine another pair of classes:
#include <string>
#include <string_view>
class Weapon
{
public:
std::string m_name;
int m_baseDamage{};
Weapon(std::string_view name = "", int damage = 10)
: m_name{ name }, m_baseDamage{ damage }
{
}
const std::string& getName() const { return m_name; }
int getBaseDamage() const { return m_baseDamage; }
};
// Sword publicly inheriting Weapon
class Sword : public Weapon
{
public:
double m_sharpness{};
bool m_isTwoHanded{};
Sword(double sharpness = 1.0, bool twoHanded = false)
: m_sharpness{ sharpness },
m_isTwoHanded{ twoHanded }
{
}
};
As we'd previously written it, Sword only initializes its own members and doesn't specify a Weapon constructor to use. This means every Sword we create will use the default Weapon constructor, which initializes the name to blank and damage to 10. Because it makes sense to give our Sword a name and damage when we create them, we should modify this constructor to add those parameters.
Here are our updated classes that use private members, with the Sword class calling the appropriate Weapon constructor to initialize the inherited Weapon member variables:
#include <iostream>
#include <string>
#include <string_view>
class Weapon
{
private:
std::string m_name;
int m_baseDamage{};
public:
Weapon(std::string_view name = "", int damage = 10)
: m_name{ name }, m_baseDamage{ damage }
{
}
const std::string& getName() const { return m_name; }
int getBaseDamage() const { return m_baseDamage; }
};
// Sword publicly inheriting Weapon
class Sword : public Weapon
{
private:
double m_sharpness{};
bool m_isTwoHanded{};
public:
Sword(std::string_view name = "", int damage = 10,
double sharpness = 1.0, bool twoHanded = false)
: Weapon{ name, damage } // call Weapon(std::string_view, int) to initialize these fields
, m_sharpness{ sharpness }, m_isTwoHanded{ twoHanded }
{
}
double getSharpness() const { return m_sharpness; }
bool isTwoHanded() const { return m_isTwoHanded; }
};
Now we can create swords like this:
#include <iostream>
int main()
{
Sword excalibur{ "Excalibur", 100, 2.5, true };
std::cout << excalibur.getName() << '\n';
std::cout << excalibur.getBaseDamage() << '\n';
std::cout << excalibur.getSharpness() << '\n';
std::cout << excalibur.isTwoHanded() << '\n';
return 0;
}
This outputs:
Excalibur 100 2.5 1
As you can see, the name and damage from the base class were properly initialized, as were the sharpness and two-handed flag from the derived class.
Inheritance chains
Classes in an inheritance chain work in exactly the same way.
#include <iostream>
class GameUnit
{
public:
GameUnit(int id)
{
std::cout << "GameUnit: " << id << '\n';
}
};
class Character: public GameUnit
{
public:
Character(int id, int level)
: GameUnit{ id }
{
std::cout << "Character: " << level << '\n';
}
};
class Hero: public Character
{
public:
Hero(int id, int level, char rank)
: Character{ id, level }
{
std::cout << "Hero: " << rank << '\n';
}
};
int main()
{
Hero protagonist{ 42, 10, 'S' };
return 0;
}
In this example, class Hero is derived from class Character, which is derived from class GameUnit. So what happens when we instantiate an object of class Hero?
First, main() calls Hero(int, int, char). The Hero constructor calls Character(int, int). The Character constructor calls GameUnit(int). Because GameUnit does not inherit from anybody, this is the first class we'll construct. GameUnit is constructed, prints the value 42, and returns control to Character. Character is constructed, prints the value 10, and returns control to Hero. Hero is constructed, prints the value 'S', and returns control to main(). And we're done!
Thus, this program prints:
GameUnit: 42 Character: 10 Hero: S
It's worth mentioning that constructors can only call constructors from their immediate parent/base class. Consequently, the Hero constructor could not call or pass parameters to the GameUnit constructor directly. The Hero constructor can only call the Character constructor (which has the responsibility of calling the GameUnit constructor).
Destructors
When a derived class is destroyed, each destructor is called in the reverse order of construction. In the above example, when protagonist is destroyed, the Hero destructor is called first, then the Character destructor, then the GameUnit destructor.
If your base class has virtual functions, your destructor should also be virtual, otherwise undefined behavior will result in certain cases. We cover this case in the Virtual Destructors lesson.
Summary
Calling base class constructors: Derived class constructors can explicitly specify which base class constructor to call by adding a call to the base class constructor in the member initializer list. The syntax is DerivedClass(params) : BaseClass(baseParams), m_derivedMember(value) { }. This allows the derived class to properly initialize inherited base class members.
Member initialization restrictions: C++ prevents derived classes from directly initializing inherited member variables in their member initializer lists. Variables can only be initialized in constructors belonging to the same class that owns those variables. This ensures const and reference variables are initialized only once, preventing potential conflicts where multiple derived classes could try to initialize the same base class member.
Construction process details: When instantiating a derived class object, memory is allocated for the entire object, the appropriate derived class constructor is called, the compiler looks for a base class constructor call (using default if none specified), the base class constructor's member initializer list runs, the base class constructor body executes, then control returns to the derived class where its member initializer list runs, and finally its constructor body executes.
Delegating constructor initialization: The base class constructor is called from the derived class's member initializer list and will always execute first, regardless of where it appears in the initializer list. This ensures base class members are initialized before derived class members, maintaining the proper construction order.
Private member access: Making base class members private doesn't prevent derived classes from using them through constructors and access functions. The derived class simply calls the appropriate base class constructor to initialize private base members and uses public access functions to retrieve those values when needed.
Inheritance chains: For multi-level inheritance hierarchies, constructors can only call their immediate parent's constructor. The responsibility for calling further ancestor constructors belongs to that parent class. This creates a chain where each class calls its immediate parent's constructor, ensuring proper initialization throughout the hierarchy.
Destructor order: When a derived class is destroyed, destructors are called in reverse order of construction: most-derived to most-base. This ensures derived class cleanup happens before base class cleanup, preventing issues with accessing base class resources that may have been cleaned up prematurely.
At this point, you now understand enough about C++ inheritance to create your own inherited classes!
Initializing Base and Derived Classes - Quiz
Test your understanding of the lesson.
Practice Exercises
Vehicle Registration System
Create a Vehicle base class with registration number and year. Create a Car derived class that adds the number of doors. The Car constructor must properly initialize both base and derived class members.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!