C++ inheritance crash course

Inheritance

Inheritance is the mechanism that the attributes and methods of a base class are passed to a derived class. Here, all useful details of C++ inheritance are mentioned with examples.

class Base{
public:
    int i;
    void doSomething(){};
};
class Derived: public Base{};

Derived d;
d.i =10;
d.doSomething();

In the above example, Derived inherits i and doSomething from base class;

Public, protected and private inheritance

These specifiers modify the accessibility of inherited members as below

Base member specifierInheritance modifierInherited member specifier
publicpublicpublic
protectedprotected
privateno access
publicprotectedprotected
protectedprotected
privateno access
publicprivateprivate
protectedprivate
privateno access

Example

class Base{
public:
  int x;
protected:
  int y;
private:
  int z;
};
class A: public Base{
  // Base members are accessible as
  // public x
  // protected y
  // no access to z
};
class B: protected Base{
  // Base members are accessible as
  // protected x
  // protected y
  // no access to z
};
class C: private Base{
  // Base members are accessible as
  // private x
  // private y
  // no access to z
};
  • Access specifiers can be relaxer or more restricted when a method overridden in the derived class:
class Base{
private:
  virtual void DoSomething(){}
};

class Derived: public Base{
Public:
   void DoSomething() override {}
};

Base b;
Derived d;
b.DoSomething(); // Error, not accessible
d.DoSomething(); // OK

Struct vs Class

struct inheritance is public by default, and class inheritance is private:

class Base{};
class Derived: public Base{};

is similar to

struct Base{};
struct derived: Base{};

Besides inheritance, the members of struct are public by default in contrast to class members which are private.

Virtual Method

Use virtual keyword to let the compiler know the method can be overridden in a future derived class. To complete overriding, use the keyword override in the derived class method:

struct Base
{
   virtual void Move() {cout<<"I am Walking ...";}
};
struct Derived : Base
{
   void Move() override  {cout << "I am running" << std::endl;}
};

int main() {
   Base* b = new Derived();
   Derived* d = new Derived();
   b->Move(); // I am running
   d->Move(); // I am running
}
  • If keyword override not used, the program runs the same. It is there to for safety .i.e we emphasis on Move must be overriden, and compiler will let us know if it is OK.

Interface (abstract class)

An interface mentions the methods of a class but not giving the details. In the example below, ComputeArea is a pure virtual function: no details of how it works is given, we only know its signature.

class Shape{
public:
    virtual double ComputeArea()=0
};

It expects derived classes define how they implement the methods. In contrast to other languages like C#, C++ doesn’t have keywords like abstract or interface for an abstract class.

class Circle: public Shape{
    double r;
    public:
    Circle(double _r):r(_r){}
    double ComputeArea() override {return 3.14*r*r}
};

class Square: public Shape{
    double s;
    public:
    Square(double _s):s(_s){}
    double ComputeArea() override {return s*s}
};

The interface cannot be used to create objects

Shape s;  //Error

However, it can be used to create a generic piece of code that works with all different derived classes:

void displayArea(Shape& shape){
      cout<<shape.ComputeArea();
}

Circle c(1.0);
Square s(1.0);

displayArea(c); // 3.14
displayArea(s); // 1.0

Interfaces can help to create loosely-coupled systems see an example with C#.

Final class or method

A final class cannot be derived and a final method cannot be overridden. Use final keyword for this purpose:

struct Base
{
     virtual void Move()  {cout<<"I am Walking ...";}
};
struct Derived final : Base
{
    virtual void Move() final override{cout << "I am running";}
};
struct Extended : Derived // Error
{
        void Move() override {cout<<"...";} //Error 
};

Making classes final increases the efficiency and safety of the code.

Order of constructors call in inheritance hierarchy

  • If derived class constructor called, base default constructor is called first automatically (implicitly):
class Base{
  public:
  Base(){cout<<"Base called.";}
  Base(int j){cout<<"Parameter Base called.";}
};
class Derived: public Base{
  public: Derived(int j){cout<<"Derived called.";}
};
Derived d(0);
// Base called.  Derived called.
  • To enforce call to parameterised constructor of the base class, it should be explicitly mentioned in derived class constructor:
class Base{
  public:
  Base(){cout<<"Base called.";}
  Base(int j){cout<<"Parameterised Base called.";}
};
class Derived: public Base{
  public:
  Derived(int j):Base(j){cout<<"Derived called.";}
};
Derived d(0);
// Parameterised Base called.  Derived called.
  • A virtual method, called in the constructor of the base class, will not be overridden when a call to the base constructor is a consequence of derived class construction.
class Base{
  public:
  Base(){cout<<"Base called."; Do();}
  virtual void Do(){cout<<"Base does something.";}
};
class Derived: public Base{
  public:
  Derived() {cout<<"Derived called.";}
  void Do() override {cout<<"Derived does something.";}
};

int main(){
 Derived d;
}
//Base called.  Base does something.  Derived called.
  • In multiple inheritance, the base constructors are called in the order of appearance in the class definition:
class Base1{
  public: Base1(){cout<<"Base1.";}
};
class Base2{
  public: Base2(){cout<<"Base2.";}
};

// The order below is important
class Derived: public Base1, public Base2 {
    public:
    Derived():Base2(),Base1(){} // Not here
};

Derived d; // Base1. Base2.   

Order of destructors call in inheritance hierarchy

The order of destructor calls is in the opposite direction of constructor calls.

struct Base1{
  virtual ~Base1(){std::cout<<"Base1.";}
};
struct Base2{
  virtual ~Base2(){std::cout<<"Base2.";}
};

struct Derived: public Base1, public Base2 {
  ~Derived(){std::cout<<"Derived.";} 
};

int main(){
    Derived d; // Derived.Base2.Base1.  
}

The same as constructors, polymorphism doesn’t work for calls to virtaul functions within a base’s destructors:

struct Base{
  virtual ~Base(){std::cout<<"Base."; Do();}
  virtual void Do(){std::cout<<"Base does something.";}
};
struct Derived:  Base{
  ~Derived() {std::cout<<"Derived.";}
  void Do() override {std::cout<<"Derived does something.";}
};
int main(){
    Derived d; //Derived.Base.Base does something.
}

Private constructors

A private constructor is used for named constructor idiom, i.e., choosing different names for constructors. In the example below, a mass object can be constructed with different units of kilogram or pound:

#include <iostream>

class Mass {
  public:
  // mass in pound
  static Mass lb(double m){ 
      return Mass{m*0.453};
  }
  // mass in kilogram
  static Mass kg(double m){return Mass{m};}
  
  void print(){std::cout<<value<<" kg\n";}
  
  private:
  Mass(double m):value{m}{};
  double value; // in kg
};

int main() {
 
    auto mass = Mass::lb(120);
    mass.print(); // 54.36 kg

 return 0;
}

It is important to hide the general constructor and implement it via special builder (static) functions.

Protected constructors

If a constructor is designed to be hidden (not public), and it is needed to construct a derived class, then it must be protected. For example, this makes errors:

class Base {
  Base(){}; // private constructor
};

class Derived: public Base {
    public: Derived(){};
};

int main() {
 Base b; // Error: Base::Base() is private
 Derived d; // Error: Base::Base() is private
 return 0;
}

In the above example, to construct d, first Base’s constructor is called which is private and unavailable.

But inheritance works with protected constructors because a derived class has access to the base’s constructor:

class Base {
  protected:
  Base(){}; 
};

class Derived: public Base {
    public: Derived(){};
};

int main() {
 Base b; // Error: Base::Base() is protected.
 Derived d; // OK: Base::Base() is protected and can be called within Derived
 return 0;
}

Deleted constructor

If you don’t like a constructor to be available at all, instead of making it private, just delete it:

class Book {
  public:
  Book()=delete;
  /* The rest ... */
};
Book b; //Error: call to deleted constructor

Private destructor

Destructors don’t accept arguments, so their complexity is less than constructors. Similiar to private constructors, a destructor can be hidden as private and delegate its act to another public or friend function. Honestly, I am not sure how useful this can be.

Virtual vs protected destructor

A base class destructor must be protected or virtual. Why? see this

struct Base {
  ~Base(){std::cout<<"Base";}
};

struct Derived: Base {
  ~Derived(){std::cout<<"Derived";}
};

int main() {
 Base* b = new Derived{};
 delete b; // "Base"
 return 0;
}

The object pointed by b is of Derived class, therefore, we expect its destructor is called but that is not what happend. This is undefined behavior. To solve it, we make the Base’s destructor virtual:

struct Base {
  virtual ~Base(){std::cout<<"Base";}
};

struct Derived: Base {
  ~Derived(){std::cout<<"Derived";}
};

int main() {
 Base* b = new Derived{};
 delete b; // Derived Base
 return 0;
}

When b is deleted, the vtable of b is checked and the correct destructor, ~Derived(), is called, which consequently calls the destructors of its members and base classes.

Another (less popular) way is to hide base’s destructor by making it protected and avoid polymorphic flexibility:

struct Base {
  protected: ~Base(){std::cout<<"Base";}
};

struct Derived: Base {
  ~Derived(){std::cout<<"Derived ";}
};

int main() {
 Base* b = new Derived{};
 Derived* d = new Derived{};
 delete b; // Error: protected dtor
 delete d; // OK: "Derived Base"
 return 0;
}

Note that ~Base() cannot be private because a Derived object has no access to it to destruct its base.

Multiple inheritance

  • It can be used for merge feature classes to create a complex system.

  • It can be done with the aid of interfaces (abstract classes) or implementations.

  • Note some programming languages like C# only support multiple inheritance of interfaces but Not multiple inheritance of class implementations and they are successful.

  • Example of multiple inheritance of interfaces:

struct IMotion {
	virtual void Walk() = 0;
};

struct ISpeech {
	virtual void Talk() = 0;
};

struct Robot : IMotion, ISpeech {
  void Walk() override {cout<<"Robot is walking... ";}
  void Talk() override {cout<<"Robot is talking... ";}
};

// Anything with interface of ISpeech accepted
void SpeechPlayer(ISpeech& speech){
  cout<< "playing the speech: ";
  speech.Talk();
}

int main(){
Robot robot;
SpeechPlayer(robot);}

  • An example of multiple inheritance of implementations:
struct Motion {
	void Walk() {cout<<"Walking ...";};
};

struct Speech {
	void Talk()  {cout<<"Talking ... \n";}
};

struct Robot : Motion, Speech { };

int main(){
  Robot r;
  r.Walk(); // Walking ...
  r.Talk(); // Talking ...
}
  • An example of multiple inheritance of implementations with interface:
struct IMotion {
	virtual void Walk() = 0;
};

struct Motion: IMotion {
	void Walk() {cout<<"Walking ...";};
};

struct ISpeech {
	virtual void Talk() = 0;
};

struct Speech : ISpeech{
	void Talk()  {cout<<"Talking ... \n";}
};

struct Robot : Motion, Speech { };

void SpeechPlayer(ISpeech& speech){
  cout<< "playing the speech: ";
  speech.Talk();
}

int main(){
  Robot robot;
  SpeechPlayer(robot);
}
  • Multiple inheritance can be used instead of Bridge design pattern if fine grain control over mixing classes needed. Bridge Design:
struct IMotion {
	virtual void Move() = 0;
};

struct TwoLegs: IMotion {
	 void Move() {cout<<"Two-leg robot moving ... \n";};
};
struct FourLegs: IMotion {
	 void Move() {cout<<"Four-leg robot moving ...";};
};

struct CommunicatingRobot {    
    CommunicatingRobot(IMotion* motion):_motion(motion){}
    void Move(){ _motion->Move();}
    virtual void Communicate() = 0;
    private:
    IMotion* _motion;
};

struct TalkingRobot: CommunicatingRobot{
    TalkingRobot(IMotion* motion):CommunicatingRobot(motion){}
    void Communicate(){cout<<"Talking ...";}
};

struct DisplayRobot: CommunicatingRobot{
    DisplayRobot(IMotion* motion):CommunicatingRobot(motion){}
    void Communicate(){cout<<"Display ...";}
};


int main(){
  TalkingRobot twoLegTalkingRobot(new TwoLegs());
  TalkingRobot fourLegTalkingRobot(new FourLegs());
  DisplayRobot twoLegDisplayRobot(new TwoLegs());
  DisplayRobot fourLegDisplayRobot(new FourLegs());
  twoLegTalkingRobot.Move();//Two-leg robot moving ...
  twoLegTalkingRobot.Communicate(); // Talking ...
}

Above example using multiple inheritance

struct IMotion {
	virtual void Move() = 0;
};

struct TwoLegs: IMotion {
	 virtual void Move() {cout<<"Two-leg robot moving ... \n";};
};
struct FourLegs: IMotion {
	 virtual void Move() {cout<<"Four-leg robot moving ...";};
};

struct ICommunicatingRobot {    
    virtual void Communicate() = 0;
};

struct TalkingRobot: ICommunicatingRobot{
    virtual void Communicate(){cout<<"Talking ...";}
};

struct DisplayRobot: ICommunicatingRobot{
    virtual void Communicate(){cout<<"Display ...";}
};

struct TwoLegTalkingRobot: TwoLegs, TalkingRobot {/* specific methods for this composition */};
struct FourLegTalkingRobot: FourLegs, TalkingRobot {/* specific methods for this composition */};
struct TwoLegDisplayRobot: TwoLegs, DisplayRobot {/* specific methods for this composition */};
struct FourLegDisplayRobot: FourLegs, DisplayRobot {/* specific methods for this composition */};

int main(){
  TwoLegTalkingRobot robo;
  robo.Move();//Two-leg robot moving ...
  robo.Communicate(); // Talking ...
}

Dreaded diamond

To explain this, lets look at below example:

struct Base{ int i;};
struct D1: Base {};
struct D2: Base {};
struct Target: D1, D2 {};

int main(){
Target  t;
t.i = 1; // Error: request for member ‘i’ is ambiguous 
}

The multiple inhertance graph of the example above is shown below .

    Base    
     /\
    /  \
 D1     D2
    \  /
     \/
   Target

D1 and D2 pass two copy of Base to target. So when i is called the compiler cannot figure out which copy to call. To avoid this, use keyword virtual in definition of D1 and D2, therefore, only one copy of Base is passed to Target:

struct Base{ int i;};
struct D1: virtual Base {};
struct D2: virtual Base {};
struct Target: D1, D2 {};

int main(){
Target  t;
t.i = 1; // works fine :)
}

Hiding rule

A method in a derived class cannot implicitly overload a method of the base class. The compiler only looks in the scope of the derived class for function overloading. In this case the base method should be explicitly mentioned in the derived class, see example below:

struct Base
{
    virtual void f(int i){cout<<"integer function called.";}
};
struct Derived : Base
{
    void f(double x){cout<<"double function called.";}
};

struct OverloadingDerived : Base
{
    using Base::f; // Let compiler consider base method for overloading
    void f(double x){cout<<"double function called.";}
};
 

int main() {
    Derived d;
    int j = 1;
    double x = 0.0;
    d.f(j); // double function called. :( 
    d.f(x); // double function called.

    OverloadingDerived o;
    o.f(j); //integer function called.
    o.f(x); //double function called.
}
Tags ➡ C++

Subscribe

I notify you of my new posts

Latest Posts

Comments

0 comment