Categories
Computer Science / Information Technology Language: C++

Inheritance

Overview

Inheritance is a powerful feature of object-oriented programming that allows you to reuse code from existing classes. In C++, inheritance is used to create new classes (derived classes) from existing classes (base classes). The derived class inherits all the properties and methods of the base class, and can also add its own properties and methods.

For example, let’s say you have a class called Animal that has properties like name, age, and species, and methods like eat() and sleep(). You could then create a derived class called Dog that inherits all the properties and methods of Animal, and adds its own properties and methods, like breed and colour.

Inheritance is a powerful tool that can help us to write more efficient and maintainable code. It can also help us to organise our code in a more logical way.

Here are some of the benefits of using inheritance in C++:

  • Code reuse: Inheritance allows you to reuse code from existing classes, which can save you time and effort.
  • Code organisation: Inheritance can help you to organise code in a more logical way, making it easier to understand and maintain.
  • Flexibility: Inheritance gives you the flexibility to customise classes to meet specific needs.
  • Reusability: Inheritance makes it easy to create new classes from existing classes, which can save you time and effort when developing new applications.

Inheritance is a powerful feature of C++ that can be used to improve the quality, efficiency, and maintainability of code. Inheritance can also be thought of as a way of grouping classes together based on their common attributes. A base class can be created that contains all of the common attributes, and then derived classes can be created that inherit from the base class. This allows us to reuse code and keep our code organised.

In the following exercise, identify which class could be the base class, what could be the attributes of the base class and the derived classes:

  1. Line, Oval, Shape, Circle, Square.
  2. Savings Account, Account, Trust Account, Checking Account, Current Account.
  3. Employee, Student, Faculty, Staff, Administrator, Person.
  4. Level Boss, Hero, Super Player, Player.

Syntax

Considering a basic example of a base class getting inherited by a derived class, the following is the syntax that realises it:

class BaseClass {
  // Base class members
};

class DerivedClass : public BaseClass {
  // Derived class members
};

Example

Let’s consider an example of a simple Bank Account system. The classes to be written would be Savings Account, Checking Account and Trust Account. We can see that all of these accounts have the following things in common:

  1. Balance
  2. Transaction details
  3. Account number

Hence, the following would be a possible structure of Inheritance for this example:

class Account {
	// balance, transaction details, account number, ...
};

class Savings_Account : public Account {
	// Interest rate, specialised withdrawal, ...
};

class Checking_Account : public Account {
	// minimum_balance, per check fee, specialised withdrawal, ...
};

class Trust_Account : public Account {
	// interest rate, specialised withdrawal, ...
};

Types of Inheritance

There are five types of inheritance in C++:

Single inheritance

Single inheritance is a type of inheritance in which a derived class inherits from a single base class. The derived class inherits all of the members of the base class, including its methods, variables, and constructors.

For example, let’s say we have a class called Animal that has a method called eat(). We could then create a derived class called Dog that inherits from Animal. The Dog class would also have the eat() method, even though it is not defined in the Dog class itself.

Here is an example of single inheritance in C++:

class Animal {
 public:
  void eat() {
    std::cout << "I am eating." << std::endl;
  }
};

class Dog : public Animal {
};

int main() {
  Dog myDog;
  myDog.eat(); // Prints "I am eating."
  return 0;
}

In this example, the Animal class is the base class, and the Dog class is the derived class. The Dog class inherits the eat() method from the Animal class.

Single inheritance is a powerful feature of object-oriented programming that can be used to reuse code and organise code in a more logical way.

Multiple inheritance

Multiple inheritance is a type of inheritance in which a derived class inherits from multiple base classes. The derived class inherits all of the members of the base classes, including its methods, variables, and constructors.

For example, let’s take up the same example, a class called Animal that has a method called eat(). We could also have a class called Mammal that has a method called giveBirth(). We could then create a derived class called Dog that inherits from both Animal and Mammal. The Dog class would have both the eat() and giveBirth() methods, even though they are not defined in the Dog class itself.

Here is an example of multiple inheritance in C++:

class Animal {
 public:
  void eat() {
    std::cout << "I am eating." << std::endl;
  }
};

class Mammal {
 public:
  void giveBirth() {
    std::cout << "I am giving birth." << std::endl;
  }
};

class Dog : public Animal, public Mammal {
};

int main() {
  Dog myDog;
  myDog.eat(); // Prints "I am eating."
  myDog.giveBirth(); // Prints "I am giving birth."
  return 0;
}

In this example, the Dog class inherits the eat() method from the Animal class and the giveBirth() method from the Mammal class.

Multilevel inheritance

Multilevel inheritance is a type of inheritance in which a derived class inherits from a base class that itself inherits from another base class. The derived class inherits all of the members of the base classes, including its methods, variables, and constructors.

For example, let’s say we have a class called Animal that has a method called eat(). We could also have a class called Mammal that inherits from Animal and has a method called giveBirth(). We could then create a derived class called Dog that inherits from Mammal. The Dog class would have both the eat() and giveBirth() methods, even though they are not defined in the Dog class itself.

Here is an example of multilevel inheritance in C++:

class Animal {
 public:
  void eat() {
    std::cout << "I am eating." << std::endl;
  }
};

class Mammal : public Animal {
 public:
  void giveBirth() {
    std::cout << "I am giving birth." << std::endl;
  }
};

class Dog : public Mammal {
};

int main() {
  Dog myDog;
  myDog.eat(); // Prints "I am eating."
  myDog.giveBirth(); // Prints "I am giving birth."
  return 0;
}

In this example, the Dog class inherits the eat() method from the Animal class and the giveBirth() method from the Mammal class.

Hierarchical inheritance

Hierarchical inheritance is a type of inheritance in which a derived class inherits from a single base class, and that base class is also inherited from another derived class. The derived classes are like branches on a tree, and the base class is like the trunk of the tree.

For example, let’s say we have a class called Animal that has a method called eat(). We could also have a class called Mammal that inherits from Animal and has a method called giveBirth(). We could then create two derived classes called Dog and Cat that both inherit from Mammal. The Dog class would have the eat() and giveBirth() methods, and the Cat class would have the eat() and giveBirth() methods.

Here is an example of hierarchical inheritance in C++:

class Animal {
 public:
  void eat() {
    std::cout << "I am eating." << std::endl;
  }
};

class Mammal : public Animal {
 public:
  void giveBirth() {
    std::cout << "I am giving birth." << std::endl;
  }
};

class Dog : public Mammal {
};

class Cat : public Mammal {
};

int main() {
  Dog myDog;
  myDog.eat(); // Prints "I am eating."
  myDog.giveBirth(); // Prints "I am giving birth."

  Cat myCat;
  myCat.eat(); // Prints "I am eating."
  myCat.giveBirth(); // Prints "I am giving birth."
  return 0;
}

In this example, the Dog class and the Cat class both inherit the eat() and giveBirth() methods from the Mammal class.

Hierarchical inheritance is a powerful feature of object-oriented programming that can be used to reuse code and organise code in a more logical way. It is a good choice when you have a hierarchy of classes, such as animals, mammals, dogs, and cats.

Hybrid inheritance

Hybrid inheritance is a combination of two or more types of inheritance. For example, a class could inherit from multiple base classes, or it could inherit from a base class that itself inherits from another base class.

Here is an example of hybrid inheritance in C++:

class Animal {
 public:
  void eat() {
    std::cout << "I am eating." << std::endl;
  }
};

class Mammal : public Animal {
 public:
  void giveBirth() {
    std::cout << "I am giving birth." << std::endl;
  }
};

class Dog : public Mammal {
};

class FlyingAnimal : public Animal {
 public:
  void fly() {
    std::cout << "I am flying." << std::endl;
  }
};
class Bird : public FlyingAnimal {
};

int main() {
  Dog myDog;
  myDog.eat(); // Prints "I am eating."
  myDog.giveBirth(); // Prints "I am giving birth."

  Bird myBird;
  myBird.eat(); // Prints "I am eating."
  myBird.fly(); // Prints "I am flying."
  return 0;
}

In this example, the Dog class inherits from both the Mammal class and the Animal class. The Bird class inherits from both the FlyingAnimal class and the Animal class. This is a type of hybrid inheritance.

Hybrid inheritance can be a powerful tool for organising code. It can be used to reuse code, to create more complex hierarchies of classes, and to model real-world relationships between objects.

Summary

Each type of inheritance has its own advantages and disadvantages. Single inheritance is the simplest type of inheritance, but it can be limiting if you need to reuse code from multiple classes. Multiple inheritance can be more flexible, but it can also be more complex and error-prone. Multilevel inheritance can be useful for organising code, but it can also make it more difficult to keep track of the relationships between classes. Hierarchical inheritance can be useful for representing hierarchies of objects, but it can also make it more difficult to find the code that you need. Hybrid inheritance can be the most flexible type of inheritance, but it can also be the most complex and error-prone.

The best type of inheritance to use depends on the specific needs of the project. If you are not sure which type of inheritance to use, it is best to start with single inheritance and then add more complex types of inheritance as needed.

Exercises

Single inheritance

  • Create a class called Shape with a method called draw().
  • Create a derived class called Circle that inherits from Shape.
  • Create a derived class called Rectangle that inherits from Shape.
  • In the draw() method of Circle, print “I am a circle.”
  • In the draw() method of Rectangle, print “I am a rectangle.”
  • Create an object of type Circle and call the draw() method.
  • Create an object of type Rectangle and call the draw() method.

Multiple inheritance

  • Create a class called Animal with a method called eat().
  • Create a class called Mammal with a method called giveBirth().
  • Create a class called Dog that inherits from both Animal and Mammal.
  • In the eat() method of Dog, print “I am a dog and I am eating.”
  • In the giveBirth() method of Dog, print “I am a dog and I am giving birth.”
  • Create an object of type Dog and call the eat() and giveBirth() methods.

Multilevel inheritance

  • Create a class called Animal with a method called eat().
  • Create a class called Mammal that inherits from Animal and has a method called giveBirth().
  • Create a class called Dog that inherits from Mammal.
  • In the eat() method of Dog, print “I am a dog and I am eating.”
  • In the giveBirth() method of Dog, print “I am a dog and I am giving birth.”
  • Create an object of type Dog and call the eat() and giveBirth() methods.

Hierarchical inheritance

  • Create a class called Animal with a method called eat().
  • Create a derived class called Mammal that inherits from Animal and has a method called giveBirth().
  • Create two more derived classes called Dog and Cat that both inherit from Mammal.
  • In the eat() method of Dog, print “I am a dog and I am eating.”
  • In the giveBirth() method of Dog, print “I am a dog and I am giving birth.”
  • In the eat() method of Cat, print “I am a cat and I am eating.”
  • In the giveBirth() method of Cat, print “I am a cat and I am giving birth.”
  • Create an object of type Dog and call the eat() and giveBirth() methods.
  • Create an object of type Cat and call the eat() and giveBirth() methods.

Hybrid inheritance

  • Create a class called Shape with a method called draw().
  • Create a class called Color with a method called setColor().
  • Create a derived class called Circle that inherits from both Shape and Color.
  • In the draw() method of Circle, print “I am a circle.”
  • In the setColor() method of Circle, print “I am setting the colour of the circle.”
  • Create an object of type Circle and call the draw() and setColor() methods.

Inheritance vs Composition

Inheritance and composition are two different ways to achieve code reuse in object-oriented programming.

Inheritance is a relationship between two classes where one class, the derived class, inherits the properties and methods of another class, the base class. The derived class is said to extend the base class.

Composition is a relationship between two classes where one class, the container class, contains an instance of another class, the contained class. The container class is said to compose the contained class.

The main difference between inheritance and composition is the relationship between the two classes. In inheritance, the derived class is a specialisation of the base class. This means that the derived class inherits all of the properties and methods of the base class, and it can also add its own properties and methods. In other words, the relationship between derived class and base class in inheritance is a “Is-A relationship”. For example,

Derived classRelationshipBase class
EmployeeIs aPerson
SavingsAccountIs aBankAccount
DogIs anAnimal
CircleIs aShape

Whereas, in composition, the container class is a collection of the contained class. This means that the container class can have multiple instances of the contained class, and it can also access the properties and methods of the contained class. In other words, the relationship between 2 classes in composition is “Has-a”. For example,

Container classRelationshipContained class
EmployeeHas aSavingsAccount
PersonHas aHouse
CircleHas aLocation
CarHas anEngine

Inheritance is a powerful tool for code reuse, but it can also lead to tight coupling between classes. This means that changes to the base class can break the derived class. Composition is a loser coupling approach to code reuse, and it is less likely to lead to problems when the base class changes.

In general, inheritance is a good choice when the derived class is a specialisation of the base class. Composition is a good choice when the container class is a collection of the contained class.

Here is a table that summarises the differences between inheritance and composition:

FeatureInheritanceComposition
Relationship between classesDerived class is a specialisation of base classContainer class is a collection of contained class
CouplingTight couplingLoose coupling
When to useWhen the derived class is a specialisation of the base classWhen the container class is a collection of the contained class

A practical example:

Exercise

Develop the following program:

  • Create a class called Animal with the following properties:
    • Name (string)
    • Age (Float)
    • Species (String)
  • Create a class called Mammal that inherits from Animal and has the following properties:
    • gives birth (Boolean)
    • nurses young (Boolean)
  • Create a class called Dog that inherits from Mammal and has the following properties:
    • Barks (Boolean)
    • wags tail (Boolean)
  • Create a class called Cat that inherits from Mammal and has the following properties:
    • Purrs (Boolean)
    • Scratches (Boolean)
  • Create a class called Zoo that contains a collection of animals.
  • Add a method to the Zoo class that allows you to add an animal to the zoo.
  • Add a method to the Zoo class that allows you to remove an animal from the zoo.
  • Add a method to the Zoo class that allows you to print a list of all the animals in the zoo.

Public, private, and protected inheritance in C++

  • Public inheritance is a type of inheritance in which the derived class inherits the public members of the base class. This means that the derived class can access the public members of the base class, and it can also add its own public members.
  • Private inheritance is a type of inheritance in which the derived class inherits the private members of the base class. This means that the derived class can access the private members of the base class, but the public and protected members of the base class are not accessible to the derived class.
  • Protected inheritance is a type of inheritance in which the derived class inherits the protected members of the base class. This means that the derived class can access the protected members of the base class, and the public members of the base class are also accessible to the derived class.

The main difference between public, private, and protected inheritance is the accessibility of the members of the base class. In public inheritance, the derived class can access all of the members of the base class. In private inheritance, the derived class can only access the private members of the base class. In protected inheritance, the derived class can access the protected members of the base class, and the public members of the base class are also accessible to the derived class.

Public inheritance is the most common type of inheritance. It is used when the derived class needs to have access to all of the members of the base class. Private inheritance is used when the derived class needs to have complete control over the base class. Protected inheritance is used when the derived class needs to have access to the base class, but it does not need to have complete control over the base class.

Here are some examples of public, private, and protected inheritance in C++:

Public inheritance:

class Base {
  public:
    int public_member;

  private:
    int private_member;

  protected:
    int protected_member;
};

class Derived : public Base {
  public:
    void derived_member() {
      std::cout << public_member; // This will compile.
      std::cout << private_member; // This will not compile.
      std::cout << protected_member; // This will compile.
    }
};

In this example, the Derived class inherits the public_member variable from the Base class. The public_member variable is accessible to the Derived class, and it is also accessible to any other classes. The private_member and protected_member variables are not accessible to the Derived class.

Private inheritance

class Base {
  public:
    int public_member;

  private:
    int private_member;

  protected:
    int protected_member;
};

class Derived : private Base {
  public:
    void derived_member() {
      std::cout << public_member; // This will not compile.
      std::cout << private_member; // This will compile.
      std::cout << protected_member; // This will not compile.
    }
};

In this example, the Derived class inherits the private_member variable from the Base class. The public_member and protected_member variables are not accessible to the Derived class.

Protected inheritance

class Base {
  public:
    int public_member;

  private:
    int private_member;

  protected:
    int protected_member;
};

class Derived : protected Base {
  public:
    void derived_member() {
      std::cout << public_member; // This will compile.
      std::cout << private_member; // This will not compile.
      std::cout << protected_member; // This will compile.
    }
};

In this example, the Derived class inherits the protected_member variable from the Base class. The public_member and private_member variables are also accessible to the Derived class.

Exercises

Private inheritance

  • Create a class called Shape with the following properties:
    • name
    • colour
  • Create a class called Circle that inherits from Shape and has the following properties:
    • radius
  • Create a class called Rectangle that inherits from Shape and has the following properties:
    • width
    • height
  • Create a method in the Circle class that calculates the area of the circle.
  • Create a method in the Rectangle class that calculates the area of the rectangle.
  • Create a main function that creates a Circle object and a Rectangle object and prints the area of each object.

Protected Inheritance

  • Create a class called Account with the following properties:
    • name
    • balance
  • Create a class called CheckingAccount that inherits from Account and has the following properties:
    • monthly fee
  • Create a class called SavingsAccount that inherits from Account and has the following properties:
    • interest rate
  • Create a method in the CheckingAccount class that deducts the monthly fee from the balance.
  • Create a method in the SavingsAccount class that adds interest to the balance.
  • Create a main function that creates a CheckingAccount object and a SavingsAccount object and deposits $100 into each account. The main function should then print the balance of each account.

Constructors and destructors in Inheritance

When a class inherits from another class, the constructors and destructors of the base class are called automatically before the constructors and destructors of the derived class are called. This is called constructor chaining. For example, consider the following code:

#include <iostream>

class Base {
    public:
        Base() {
            std::cout << "Base constructor called" << std::endl;
        }
        ~Base() {
            std::cout << "Base destructor called" << std::endl;
        }
};

class Derived : public Base {
    public:
        Derived() {
            std::cout << "Derived constructor called" << std::endl;
        }
    
        ~Derived() {
            std::cout << "Derived destructor called" << std::endl;
        }
};

int main() {
    Derived *der = new Derived();
    std::cout << std::endl << "Object is alive" << std::endl <<std::endl;
    delete der;
    std::cout << std::endl << "Object is destroyed";
    return 0;
}

Output:

Base constructor called
Derived constructor called

Object is alive

Derived destructor called
Base destructor called

Object is destroyed

When a Derived object is created, the following happens:

  1. The Base constructor is called.
  2. The Derived constructor is called.
  3. The Base destructor is called.

When a Derived object is destroyed, the following happens:

  1. The Derived destructor is called.
  2. The Base destructor is called.

Important rules

Note that a derived class does NOT inherit:

  • Base class constructor
  • Base class destructor
  • Base class overloaded assignment operators
  • Base class friend functions

However, the derived class constructors, destructors and overloaded assignment operators can invoke the base class versions. One may ask what is the difference when we just saw that the base class constructors and destructors are called implicitly? This is a key feature when the base class has overloaded constructors and the Derived class needs to choose which version of the base class constructor needs to be called. This also comes with the fact that the derived class can come with its own set of overloaded constructors. These overloaded constructors of derived class will want to invoke specific overloaded constructor in the base class. The syntax to call the base class constructor from the derived class constructor is as follows:

class Derived : public Base {
public:
  Derived() : Base() {
    // Derived class constructor body
  }
};

It is important to note that the Base class constructor must be declared before the Derived class constructor. Otherwise, the compiler will not be able to find the Base class constructor.

The syntax to call a parameterized base class constructor from a parameterized derived class constructor is as follows:

class Derived : public Base {
public:
  Derived(int x, int y) : Base(x, y) {
    // Derived class constructor body
 }
};

C++11 allows explicit inheritance of base ‘non-special’ constructors with

  • using Base::Base; anywhere in the derived class declaration.
  • Lots of rules involved, often better to define constructors yourself.

Copy, move constructors and assignment operator with derived classes

When a derived class inherits from a base class, the compiler will automatically generate a copy constructor, move constructor, and assignment operator for the derived class. However, these generated copy constructors, move constructors, and assignment operators may not be what you want. For example, the generated copy constructor may make a shallow copy of the object, which can lead to problems if the object contains pointers or references to dynamically allocated memory.

To avoid these problems, you can override the copy constructor, move constructor, and assignment operator for the derived class. When you override these functions, you need to make sure that they correctly copy or move the data from the source object to the destination object.

Here is an example of how to override the copy constructor for a derived class:

class Base {
public:
  Base() {
    std::cout << "Base constructor called\n";
  }

  Base(const Base& other) {
    std::cout << "Base copy constructor called\n";
  }

  ~Base() {
    std::cout << "Base destructor called\n";
  }
};

class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived constructor called\n";
  }

  Derived(const Derived& other) : Base(other) {
    std::cout << "Derived copy constructor called\n";
  }

  ~Derived() {
    std::cout << "Derived destructor called\n";
  }
};

In this example, the Derived class overrides the Base class copy constructor. The Derived class copy constructor first calls the Base class copy constructor to copy the base class data. Then, the Derived class copy constructor copies the derived class data.

Here is an example of how to override the move constructor for a derived class:

class Base {
public:
  Base() {
    std::cout << "Base constructor called\n";
  }

  Base(Base&& other) {
    std::cout << "Base move constructor called\n";
  }

  ~Base() {
    std::cout << "Base destructor called\n";
  }
};

class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived constructor called\n";
  }

  Derived(Derived&& other) : Base(std::move(other)) {
    std::cout << "Derived move constructor called\n";
  }

  ~Derived() {
    std::cout << "Derived destructor called\n";
  }
};

In this example, the Derived class overrides the Base class move constructor. The Derived class move constructor first calls the Base class move constructor to move the base class data. Then, the Derived class move constructor moves the derived class data.

Here is an example of how to override the assignment operator for a derived class:

class Base {
public:
  Base& operator=(const Base& other) {
    std::cout << "Base assignment operator called\n";
    return *this;
  }

  ~Base() {
    std::cout << "Base destructor called\n";
  }
};

class Derived : public Base {
public:
  Derived& operator=(const Derived& other) {
    std::cout << "Derived assignment operator called\n";
    Base::operator=(other);
    // Copy derived class data
    return *this;
  }

  ~Derived() {
    std::cout << "Derived destructor called\n";
  }
};

In this example, the Derived class overrides the Base class assignment operator. The Derived class assignment operator first calls the Base class assignment operator to copy the base class data. Then, the Derived class assignment operator copies the derived class data.

Method overriding: Redefining base class methods

Method overriding is a feature of object-oriented programming that allows a derived class to provide a different implementation of a method that is defined in its base class. This is done by defining a method in the derived class with the same name, parameter list, and return type as the method in the base class.

For example, consider the following code:

class Base {
public:
  void print() {
    std::cout << "Base::print()\n";
  }
};

class Derived : public Base {
public:
  void print() {
    std::cout << "Derived::print()\n";
  }
};

In this code, the Base class has a method called print(). The Derived class also has a method called print(). The two methods have the same name, parameter list, and return type. Therefore, the Derived class is overriding the print() method from the Base class.

When an object of the Derived class is created, the print() method from the Derived class will be called when the print() method is invoked on the object.

Method overriding is a powerful feature of object-oriented programming that allows for greater flexibility and code reuse.

Here are some additional things to keep in mind about method overriding:

  • The method in the derived class must have the same name, parameter list, and return type as the method in the base class.
  • The method in the derived class can have a different implementation than the method in the base class.
  • The method in the derived class will be called when the method is invoked on an object of the derived class.
  • The method in the base class will not be called when the method is invoked on an object of the derived class.

Static binding is a technique in which the type of a variable or expression is known at compile time. This means that the compiler can resolve the call to a method or function at compile time, rather than at runtime.

In C++, static binding is used in conjunction with inheritance. When a derived class inherits from a base class, the compiler can resolve the call to a method or function that is defined in the base class, even if the object is actually of the derived class type. The example we just saw is of static binding.

Prevention of inheritance in C++

Inheritance is a powerful feature of C++, but it can also be abused. In some cases, it may be desirable to prevent a class from being inherited from. There are a few different ways to do this in C++.

One way to prevent inheritance is to use the final keyword. The final keyword can be used to mark a class as final, which means that it cannot be inherited from. For example:

class MyClass final {
  // ...
};

Another way to prevent inheritance is to make the constructor private. If a class has a private constructor, then it cannot be inherited from. For example:

class MyClass {
  private:
    MyClass() {}
};

Finally, it is also possible to prevent inheritance by using a friend class. A friend class is a class that has access to the private and protected members of another class. This can be used to create a class that can access the private members of a base class, but cannot be inherited from. For example:

class MyClass {
  friend class MyFriendClass;

  private:
    MyClass() {}
};

class MyFriendClass {
  // ...
};

It is important to note that preventing inheritance can sometimes be useful, but it can also make it more difficult to design and implement classes. It is important to carefully consider the pros and cons of preventing inheritance before using it.

Here are some of the reasons why you might want to prevent inheritance in C++:

  • To prevent the accidental creation of subclasses that have unexpected or undesirable behaviour.
  • To enforce a strict hierarchy of classes.
  • To prevent the modification of the base class by subclasses.
  • To improve the performance of the class by preventing the creation of unnecessary objects.

Here are some of the drawbacks of preventing inheritance in C++:

  • It can make it more difficult to reuse code.
  • It can make it more difficult to implement polymorphism.
  • It can make it more difficult to test code.

Ultimately, the decision of whether or not to prevent inheritance in C++ is a design decision that should be made on a case-by-case basis.

Diamond problem of Inheritance

The diamond problem of inheritance is a situation that can arise in multiple inheritance when a derived class inherits from two or more base classes that share a common base class. It can lead to ambiguity in the derived class about which inherited function or data member to use.

Here’s an example to illustrate the diamond problem:

class A {
public:
    void foo() { std::cout << "A::foo()" << std::endl; }
};

class B : public A {
public:
    void foo() { std::cout << "B::foo()" << std::endl; }
};

class C : public A {
public:
    void foo() { std::cout << "C::foo()" << std::endl; }
};

class D : public B, public C {
public:
    // This class inherits from both B and C,
    // which both inherit from A.
};

int main() {
    D d;
    d.foo(); // This line will not compile because of ambiguity.
    return 0;
}

In this example, class A is the common base class, while class B and class C both inherit from A. Class D inherits from both B and C, which results in the diamond shape. The foo() function is defined in both B and C, and is inherited by D. When we call d.foo(), the compiler doesn’t know which implementation of foo() to use – should it use B::foo() or C::foo()? This ambiguity is the diamond problem.

To solve the diamond problem, C++ offers two methods: virtual inheritance and scope resolution. We shall see virtual inheritance in the next section. Let’s see how we can use the scope resolution and solve the diamond inheritance problem

The idea is to specify the class from which a particular function is to be called using the scope resolution operator. We can specify the base class from which we want to call the function. For example, to call the foo() function from class B, we can use the following syntax:

D::B::foo();

Similarly, to call the foo() function from class C, we can use:

D::C::foo();

By using the scope resolution operator, we can resolve the ambiguity and specify exactly which function to call. In the example we saw previously, we can use scope resolution and solve the diamond inheritance problem the following way:

class A {
public:
    void foo() { std::cout << "A::foo()" << std::endl; }
};

class B : public A {
public:
    void foo() { std::cout << "B::foo()" << std::endl; }
};

class C : public A {
public:
    void foo() { std::cout << "C::foo()" << std::endl; }
};

class D : public B, public C {
public:
    // This class inherits from both B and C,
    // which both inherit from A.
};

int main() {
    D d;
    d.B::A::foo(); // specify that we want to call the foo() function from class A in the B branch of the diamond
    return 0;
}

Difference between overloading and overriding

FeatureMethod overloadingMethod overriding
DefinitionA technique that allows a class to have multiple functions with the same name but with different parameters.A technique that allows a derived class to have a function with the same name, parameter list, and return type as a function in its base class.
InheritanceNot requiredRequired
ResolutionCompile timeRun time
AccessFunctions can be overloaded in the same classFunctions can be overridden only in the derived class
Examplevoid print (int x) and void print (char *x)void print(int x) in the base class and void print(int x) in the derived class.

Exercises

  1. Create a class called Shape with methods to calculate the perimeter and area of a shape. Create subclasses of Shape for different types of shapes, such as Circle, Rectangle, and Triangle. Each subclass should override the perimeter() and area() methods to calculate the perimeter and area of the specific shape.
  2. Create a class called Animal with a method called makeSound(). Create subclasses of Animal for different types of animals, such as Dog, Cat, and Bird. Each subclass should override the makeSound() method to make the sound of the specific animal.
  3. Create a class called Vehicle with methods to calculate the fuel efficiency, distance travelled, and maximum speed of a vehicle. Create subclasses of Vehicle for different types of vehicles, such as Car, Truck, and Motorcycle. Each subclass should override the fuelEfficiency(), distanceTraveled(), and maximumSpeed() methods to calculate the specific values for the type of vehicle.

Interview Questions

  1. What is inheritance in C++ and why is it useful?
  2. What are the different types of inheritance in C++?
  3. What is the syntax for inheriting a class in C++?
  4. Can multiple inheritance be achieved in C++? If so, how?
  5. What is the difference between private, protected, and public inheritance in C++?
  6. How do you prevent inheritance in C++?
  7. What is the diamond problem in C++ inheritance and how do you solve it?
  8. How do you access the members of a base class in a derived class?
  9. What is function overriding in C++ inheritance?
  10. What is the difference between function overloading and function overriding in C++?
  11. Can a derived class be used as a base class in C++? If so, how?
  12. What are the advantages and disadvantages of using inheritance in C++?
  13. How does C++ inheritance differ from Java inheritance?
  14. What is the output of the following code:
#include <iostream>
class A {
public:
  void foo() {
    std::cout << "This is foo in A\n";
  }
};

class B : public A {
public:
  void foo() {
    std::cout << "This is foo in B\n";
  }
};
class C : public B {};

int main() {
  C c;
  c.foo();
  return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

You cannot copy content of this page