Inheritance
From any class, you can make subclasses from that class.
- The original class is also known as the base class or the superclass
- The subclass(es) is also known as the derived class(es)
Subclasses inherit non-private
members of their superclasses.* This means that the subclasses can access the superclasses' public
or protected
"data" or "functions" as if they were their own.
Subclasses can also define new members, and/or override inherited members. (The members defined in the subclass will be chosen over inherited members.)
(*) And non-private
members of their superclasses' superclasses, if the inheritance type (public, protected, or private) allows it.
Defining subclasses
struct
here to just simplify the code (making the access specifiers public
by default). You may use class
in real-world scenario.Single inheritance
The most common type of inheritance is single inheritance, where a subclass only inherit from one superclass.
Below, we define StudentUser
and TeacherUser
as subclasses of User
.
struct User {
char name[255];
};
struct StudentUser : User {
int score = -1;
void printReport() {
printf("Student %s's score is %d\n", name, score);
}
};
struct TeacherUser : User {
char subject_taught[255];
void printReport() {
printf("Teacher %s teaches %s\n", name, subject_taught);
}
};
Multiple inheritance
You can have one subclass inherits from multiple superclasses, however this pattern is not recommended (hard to maintain).
struct USBDevice {
int id = 0;
int getID() {
return id;
}
};
struct NetworkDevice {
int id = 0;
int getID() {
return id;
}
};
// Inheriting from two superclasses
struct WirelessAdapter : USBDevice, NetworkDevice {
WirelessAdapter(int usbDeviceID, int networkDeviceID)
: USBDevice{usbDeviceID},
NetworkDevice{networkDeviceID}
{}
};
Specifying public, protected, or private inheritance
You can specify the type of inheritance (public
, protected
, or private
) before the superclass' name. By default, if you do not specify the type of inheritance:
- If the subclass is defined using
struct
, it will use public inheritance - If the subclass is defined using
class
, it will use private inheritance
struct StudentUser : public User {};
struct WirelessAdapter : public USBDevice, public NetworkDevice {};
Public inheritance
From the subclass' perspective, when you try to access the inherited members through the subclass (recall that private
members are not inherited):
- The superclass'
public
members staypublic
- The superclass'
protected
members stayprotected
Protected inheritance
From the subclass' perspective, when you try to access the inherited members through the subclass (recall that private
members are not inherited):
- The superclass'
public
members becomeprotected
- The superclass'
protected
members stayprotected
Private inheritance
From the subclass' perspective, when you try to access the inherited members through the subclass (recall that private
members are not inherited):
- The superclass'
public
members becomeprivate
- The superclass'
protected
members becomeprivate
Initializing superclass
Constructor call order
When you instantiate a subclass, the superclass' constructor is called first, then the subclass' constructor ("constructors are called from top to bottom").
Initializing superclass via member initializer lists
You CANNOT initialize inherited members from the subclass' member initializer lists. Instead, you just initialize the supcerclass directly from the subclass' member initializer lists.
struct BaseClass {
int base_id_a;
int base_id_b;
BaseClass(int base_id_a, int base_id_b)
: base_id_a{base_id_a},
base_id_b{base_id_b}
{}
};
// We choose the following constructor of `BaseClass`
// BaseClass(int base_id_a, int base_id_b)
struct DerivedClass : BaseClass {
int derived_id;
DerivedClass(int derived_id, int base_id_a, int base_id_b)
: BaseClass{base_id_a, base_id_b},
derived_id{derived_id}
{}
};
int main() {
DerivedClass dc = { 1, 2, 3 };
printf("%d %d %d\n", dc.derived_id, dc.base_id_a, dc.base_id_b);
//=> 1 2 3
}
Overriding superclass
On top of defining their own "data" and "function" members, within subclass definitions, you can also do the following.
Changing inherited members' access specifier
Use the using
keyword, mentioning the inherited member's name below the appropriate access specifier.
class Base {
protected:
int secret_id;
int getSecretID() {
return secret_id;
}
public:
Base(int secret_id) : secret_id{secret_id} {}
};
class Derived : Base {
public:
Derived(int secret_id) : Base{secret_id} {}
// Make `secret_id` and `getSecretID` public
// (because we place this under "public:")
using Base::secret_id;
using Base::getSecretID;
};
int main() {
Derived derived = { 123 };
// Normally, you can't do this because these members are protected
// (but we changed them to public using the `using` declaration
printf("%d\n", derived.secret_id); //=> 123
printf("%d\n", derived.getSecretID()); //=> 123
}
Deleting an inherited function
Assign = delete
to the function. Only function members can be deleted.
Once you delete an inherited function, you can no longer call that function inside the subclass or publicly through the subclass.**
class Base {
private:
int secret_id;
public:
int getSecretID() {
return secret_id;
}
Base(int secret_id) : secret_id{secret_id} {}
};
// We use public inheritance here to show
// how you can call the deleted function through
// the base class (using `static_cast<Base&>`)
class Derived : public Base {
public:
Derived(int secret_id) : Base{secret_id} {}
int getSecretID() = delete;
};
int main() {
Derived derived = { 123 };
// The following line causes compile error
// (Attempt to use a deleted function):
// printf("%d\n", derived.getSecretID()); //=> COMPILE ERROR!
}
(**) This does not prevent somebody from accessing the fuction through the superclass, e.g. by casting the subclass as the superclass: (static_cast<Base&>(derived)).getSecretID()
***
(***) This requires Derived
to use public inheritance from Base
, otherwise you will get "Cannot cast 'Derived' to its private base class 'Base'" compilation error
Overriding an inherited function
Just reimplement the function in the subclass.
Note that this will completely "replace" the inherited function from the subclass' perspective.
struct Base {
void printMessage() {
printf("I'm base!\n");
}
};
struct Derived : Base {
void printMessage() {
printf("I'm derived!\n");
}
};
int main() {
Derived d = {};
d.printMessage();
//=> I'm derived!
}
Calling an inherited function
You can call a superclass' function by scoping them with the superclass' name (even after you have overridden them):
struct Base {
void printMessage() {
printf("I'm base!\n");
}
};
struct Derived : Base {
void printMessage() {
Base::printMessage();
printf("I'm derived!\n");
}
};
int main() {
Derived d = {};
d.printMessage();
//=> I'm base!
//=> I'm derived!
}
Working with subclasses
Instantiating subclasses
Instantiate them just like any other classes.
// Using the default constructor
// (initializing all public members of the class)
StudentUser student = { "Alice", 99 };
TeacherUser teacher = { "Bob", "math" };
student.printReport();
//=> Student Alice's score is 99
teacher.printReport();
//=> Teacher Bob teaches math
Copying a subclass-type object into a superclass-type object (object slicing)
You can assign a subclass-type object into its superclass-type object. Semantically, you are copying the superclass-part of the object while "slicing off" the subclass-part of the object (this is known as object slicing).
Object slicing does not happen with pointers and references, because you are not copying the object into its superclass-type.
struct User {
int id;
char name[255];
char* getType() {
static char type[] = "User";
return type;
}
};
struct StudentUser : User {
int score = -1;
char* getType() {
static char type[] = "StudentUser";
return type;
}
};
// Object slicing may happen here
// (When you assign type of `StudentUser` to `User`,
// it creates a copy, but only for the superclass part)
void printUserInformation(User user) {
printf("%s: %s\n", user.getType(), user.name);
}
int main() {
User user = { 1, "Eve" };
StudentUser student_user = { 2, "Alice", 99 };
printUserInformation(user); //=> User: Eve
printUserInformation(student_user); //=> User: Alice
// The expression below creates a COPY
// of `User` from `StudentUser`
User user_copy = student_user;
user_copy.id = 555;
// We can proof that it's a copy by modifying
// one of its primitive member data
printf("%d\n", student_user.id); //=> 2
printf("%d\n", user_copy.id); //=> 555
}
Using pointers and references to superclass-type
You can use pointers and references to refer to the superclass "part" of a subclass object. However, you can only access the superclass' (and its superclass') members from the reference or pointer.
struct User {
char name[255];
};
struct StudentUser : User {
int score = -1;
};
int main() {
StudentUser student_user = { "Elena", 85 };
StudentUser* student_user_ptr = &student_user;
// These are okay:
User* user_ptr = student_user_ptr;
User& user_ref = student_user;
// They point to the same address,
printf("%p\n", student_user_ptr); //=> 0x7ffeefbff350
printf("%p\n", user_ptr); //=> 0x7ffeefbff350
// ... but from the superclass pointer,
// you can only access superclass members
printf("%d\n", student_user_ptr->score); //=> 85
// printf("%d\n", user_ptr->score); //=> COMPILE ERROR!
}
Resolving ambigous member access in multiple inheritance
"I told you not to use multiple inheritance."
Trying to access an ambiguous member name in multiple inheritance causes compile error, so you will have to scope them (specifying which class member to use):
// Using multiple inheritance example above
// - USBDevice's `id` = 1
// - NetworkDevice's `id` = 2
WirelessAdapter wa = { 1, 2 };
// Without specifying the scope, you will get compile error
// (Member 'getID' found in multiple base classes of different types):
// printf("%d\n", wa.getID()) //=> COMPILE ERROR!
printf("%d\n", wa.NetworkDevice::getID()); //=> 2
References
- C++ Crash Course (Josh Lospinoso) — 5. Runtime Polymorphism
- 17.9 — Multiple inheritance — https://www.learncpp.com/cpp-tutorial/multiple-inheritance/
- 17.3 — Order of construction of derived classes — https://www.learncpp.com/cpp-tutorial/order-of-construction-of-derived-classes/
- 17.4 — Constructors and initialization of derived classes — https://www.learncpp.com/cpp-tutorial/constructors-and-initialization-of-derived-classes/
- 17.5 — Inheritance and access specifiers — https://www.learncpp.com/cpp-tutorial/inheritance-and-access-specifiers/
- 17.8 — Hiding inherited functionality — https://www.learncpp.com/cpp-tutorial/hiding-inherited-functionality/
- 18.9 — Object Slicing — https://www.learncpp.com/cpp-tutorial/object-slicing/
- 18.1 — Pointers and references to the base class of derived objects — https://www.learncpp.com/cpp-tutorial/pointers-and-references-to-the-base-class-of-derived-objects/