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

We use struct here to just simplify the code (making the access specifiers publicby 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 stay public
  • The superclass' protected members stay protected

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 become protected
  • The superclass' protected members stay protected

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 become private
  • The superclass' protected members become private

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