Inheritance

By Notes Vandar

4.1 Concept of Inheritance

Inheritance is one of the core concepts of Object-Oriented Programming (OOP) in C++, allowing a class (child or derived class) to inherit attributes and methods (members) from another class (parent or base class). This enables code reuse, the creation of hierarchical relationships, and the extension of existing functionality without modifying the original code.

1. Key Terms in Inheritance

  • Base Class: The class whose properties and methods are inherited by another class. Also called the parent class or superclass.
  • Derived Class: The class that inherits properties and methods from the base class. Also called the child class or subclass.
  • Public Inheritance: The most common form of inheritance, where public members of the base class become public in the derived class, and protected members remain protected.
  • Protected Inheritance: Public and protected members of the base class become protected in the derived class.
  • Private Inheritance: Public and protected members of the base class become private in the derived class.

2. Syntax of Inheritance

Here’s the basic syntax for inheritance in C++:

class BaseClass {
public:
// Base class members
};

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

3. Example of Inheritance

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Derived class inheriting from Animal
class Dog : public Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}
};

int main() {
Dog myDog;
myDog.eat(); // Inherited from Animal class
myDog.bark(); // Defined in Dog class

return 0;
}

4. Types of Inheritance

  1. Single Inheritance: A derived class inherits from one base class.
    class B : public A { };
  2. Multiple Inheritance: A derived class inherits from more than one base class.
    class C : public A, public B { };
  3. Multilevel Inheritance: A derived class inherits from another derived class.
    class B : public A { };
    class C : public B { };
  4. Hierarchical Inheritance: Multiple derived classes inherit from a single base class.
    class B : public A { };
    class C : public A { };
  5. Hybrid Inheritance: A combination of two or more types of inheritance.

5. Access Modifiers in Inheritance

  • Public Inheritance: Members of the base class retain their access levels in the derived class:
    • Public → Public
    • Protected → Protected
    • Private → Not inherited
  • Protected Inheritance: Public and protected members of the base class become protected in the derived class.
  • Private Inheritance: Public and protected members of the base class become private in the derived class.

6. Example of Access Levels

#include <iostream>
using namespace std;

// Base class
class Person {
public:
string name;
protected:
int age;
private:
int salary; // Not inherited
};

// Derived class
class Employee : public Person {
public:
void display() {
cout << “Name: ” << name << endl; // Accessible
cout << “Age: ” << age << endl; // Accessible
// cout << “Salary: ” << salary << endl; // Not accessible, private in base class
}
};

int main() {
Employee emp;
emp.name = “John”;
emp.display();

return 0;
}

7. Types of Base Class Constructors in Inheritance

  1. Default Constructor: Automatically invoked when the derived class object is created.
  2. Parameterized Constructor: Requires explicit calls within the derived class constructor.
  3. Copy Constructor: Copies the values of one object to another.

8. Advantages of Inheritance

  • Code Reusability: Allows for the reuse of existing code, reducing redundancy.
  • Extensibility: New functionalities can be added to an existing class without modifying it.
  • Hierarchical Classification: Helps in defining a natural hierarchy of classes.

9. Disadvantages of Inheritance

  • Tight Coupling: Derived classes are tightly coupled to base classes, which can make code more rigid and difficult to modify.
  • Increased Complexity: Deep inheritance hierarchies can lead to more complex and harder-to-manage code.

 

4.2 Base and Derived Class

In Object-Oriented Programming (OOP), Base Class and Derived Class are central concepts in the inheritance model. A Base Class is a class whose members (data and functions) are inherited by another class, while a Derived Class is a class that inherits those members.

1. Base Class

A Base Class (also called parent class, superclass, or ancestor class) is the class that serves as a template for other classes. It contains attributes (data members) and methods (member functions) that can be shared with the derived class.

  • Syntax:
    class BaseClass {
    public:
    // Base class members
    void display() {
    cout << “This is the base class.” << endl;
    }
    };

2. Derived Class

A Derived Class (also called child class or subclass) is a class that inherits the attributes and methods of a base class. It can extend or override the functionalities of the base class.

  • Syntax:
    class DerivedClass : public BaseClass {
    public:
    // Derived class members
    void display() {
    cout << “This is the derived class.” << endl;
    }
    };

3. Example of Base and Derived Classes

Let’s look at a simple example that demonstrates the relationship between a base and derived class:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}
};

int main() {
Dog myDog;

// Access base class function
myDog.eat(); // Output: This animal is eating.

// Access derived class function
myDog.bark(); // Output: The dog is barking.

return 0;
}

In this example:

  • Animal is the Base Class.
  • Dog is the Derived Class that inherits the eat method from Animal and defines its own method, bark.

4. Access Specifiers in Inheritance

The way members of the base class are accessed in the derived class depends on the access specifier used in inheritance:

  1. Public Inheritance (most common):
    • Public members of the base class remain public in the derived class.
    • Protected members of the base class remain protected in the derived class.
    • Private members of the base class are not accessible to the derived class.
    class DerivedClass : public BaseClass {
    // Inherits public and protected members from BaseClass
    };
  2. Protected Inheritance:
    • Public and protected members of the base class become protected in the derived class.
    class DerivedClass : protected BaseClass {
    // Public members of BaseClass become protected
    };
  3. Private Inheritance:
    • Public and protected members of the base class become private in the derived class.
    class DerivedClass : private BaseClass {
    // Public and protected members of BaseClass become private
    };

5. Constructors and Destructors in Inheritance

  • Base Class Constructor: When an object of a derived class is created, the base class constructor is called first, followed by the derived class constructor.
  • Base Class Destructor: When the object goes out of scope, the derived class destructor is called first, followed by the base class destructor.

Example:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
Animal() {
cout << “Base class Animal constructor called.” << endl;
}
~Animal() {
cout << “Base class Animal destructor called.” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
Dog() {
cout << “Derived class Dog constructor called.” << endl;
}
~Dog() {
cout << “Derived class Dog destructor called.” << endl;
}
};

int main() {
Dog myDog;
return 0;
}

Output:

Base class Animal constructor called.
Derived class Dog constructor called.
Derived class Dog destructor called.
Base class Animal destructor called.

6. Overriding Base Class Methods

A derived class can override a base class method by providing its own implementation for the method. This is known as method overriding. The overridden method in the derived class is called instead of the one in the base class.

Example:

#include <iostream>
using namespace std;

class Animal {
public:
void sound() {
cout << “This animal makes a sound.” << endl;
}
};

class Dog : public Animal {
public:
void sound() {
cout << “The dog barks.” << endl;
}
};

int main() {
Dog myDog;
myDog.sound(); // Calls the sound() function from Dog class
return 0;
}

Output:

The dog barks.

7. Using super or BaseClassName:: to Access Base Class Methods

You can call the base class’s method from the derived class using the BaseClassName:: syntax.

Example:

#include <iostream>
using namespace std;

class Animal {
public:
void sound() {
cout << “This animal makes a sound.” << endl;
}
};

class Dog : public Animal {
public:
void sound() {
// Call base class version of sound()
Animal::sound();
cout << “The dog barks.” << endl;
}
};

int main() {
Dog myDog;
myDog.sound(); // Calls both the base class and derived class versions of sound()
return 0;
}

Output:

This animal makes a sound.
The dog barks.Chapters

4.3 Private, Public and Protected Specifier

In C++, access specifiers define the visibility and accessibility of class members (attributes and methods). The three main access specifiers are private, public, and protected. These determine how members of a class can be accessed both within and outside the class, especially when inheritance is involved.

1. Public Access Specifier

  • Public members are accessible from outside the class.
  • They can be accessed directly by any function or object.
  • In inheritance, public members remain public in the derived class (when using public inheritance).

Syntax:

class ClassName {
public:
int x; // Public member
void display() {
cout << “Public member function” << endl;
}
};

Example:

#include <iostream>
using namespace std;

class Person {
public:
string name;
int age;

void display() {
cout << “Name: ” << name << “, Age: ” << age << endl;
}
};

int main() {
Person person;
person.name = “Alice”;
person.age = 25;
person.display(); // Accessible since it’s public

return 0;
}

In this example, the name and age attributes and display() method are public, so they can be accessed outside the class.

2. Private Access Specifier

  • Private members can only be accessed from within the class.
  • They are not accessible outside the class, including derived classes (in case of inheritance).
  • In inheritance, private members of the base class are not inherited by the derived class.

Syntax:

class ClassName {
private:
int x; // Private member
void display() {
cout << “Private member function” << endl;
}
};

Example:

#include <iostream>
using namespace std;

class Person {
private:
string name;
int age;

public:
void setDetails(string personName, int personAge) {
name = personName; // Allowed, since we’re within the class
age = personAge;
}

void display() {
cout << “Name: ” << name << “, Age: ” << age << endl;
}
};

int main() {
Person person;
// person.name = “Alice”; // Error: ‘name’ is private
person.setDetails(“Alice”, 25);
person.display(); // Accessible through public function

return 0;
}

In this example, the name and age attributes are private and cannot be accessed directly outside the class. They are only accessible through public member functions like setDetails() and display().

3. Protected Access Specifier

  • Protected members are like private members in that they cannot be accessed from outside the class.
  • However, they can be accessed in derived classes.
  • In public inheritance, protected members of the base class remain protected in the derived class.

Syntax:

class ClassName {
protected:
int x; // Protected member
void display() {
cout << “Protected member function” << endl;
}
};

Example:

#include <iostream>
using namespace std;

class Person {
protected:
string name;
int age;

public:
void setDetails(string personName, int personAge) {
name = personName; // Accessible within the class
age = personAge;
}
};

class Employee : public Person {
public:
void display() {
cout << “Name: ” << name << “, Age: ” << age << endl; // Accessible in derived class
}
};

int main() {
Employee emp;
emp.setDetails(“Bob”, 30);
emp.display(); // Accessible through derived class

return 0;
}

In this example, the name and age attributes are protected in the base class Person. The derived class Employee can access and use these protected members.

4. Access Specifiers and Inheritance

When inheritance occurs, the access specifier determines how the members of the base class are inherited into the derived class:

Inheritance Type Base Class Member Derived Class Access Level
Public Inheritance Public Public
Protected Protected
Private Not inherited
Protected Inheritance Public Protected
Protected Protected
Private Not inherited
Private Inheritance Public Private
Protected Private
Private Not inherited

5. Example: Combining Access Specifiers and Inheritance

#include <iostream>
using namespace std;

class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;

public:
Base() {
publicVar = 1;
protectedVar = 2;
privateVar = 3;
}
};

class Derived : public Base {
public:
void showValues() {
cout << “Public Var: ” << publicVar << endl; // Accessible
cout << “Protected Var: ” << protectedVar << endl; // Accessible
// cout << “Private Var: ” << privateVar << endl; // Not accessible
}
};

int main() {
Derived obj;
obj.showValues();
cout << “Public Var (outside): ” << obj.publicVar << endl;
// cout << “Protected Var (outside): ” << obj.protectedVar << endl; // Not accessible
return 0;
}

In this example:

  • publicVar is accessible both inside and outside the derived class.
  • protectedVar is accessible inside the derived class but not outside.
  • privateVar is not accessible in the derived class or outside

 

4.4 Derived class declaration

In C++, a derived class is a class that inherits properties (attributes and methods) from a base class. Inheritance is a key concept in Object-Oriented Programming (OOP), enabling reusability of code and allowing one class to extend or modify the behavior of another class.

The syntax for declaring a derived class in C++ is straightforward. The derived class specifies the base class from which it inherits, along with the access level (public, protected, or private) for inherited members.

Syntax of Derived Class Declaration

class DerivedClass : accessSpecifier BaseClass {
// Additional members and methods of the derived class
};
  • DerivedClass: Name of the class that is inheriting from the base class.
  • BaseClass: The class being inherited from.
  • accessSpecifier: This specifies how the base class members will be inherited. It can be one of the following:
    • public: The public members of the base class will remain public in the derived class, and the protected members will remain protected.
    • protected: The public and protected members of the base class will become protected in the derived class.
    • private: The public and protected members of the base class will become private in the derived class.

Example: Public Inheritance

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}
};

int main() {
Dog myDog;

// Accessing base class function
myDog.eat(); // Output: This animal is eating.

// Accessing derived class function
myDog.bark(); // Output: The dog is barking.

return 0;
}

In this example:

  • Dog is the derived class, and it inherits from the Animal base class.
  • Since the inheritance is public, the public eat() function of the base class is accessible through objects of the derived class.

Types of Inheritance with Derived Class Declaration

  1. Public Inheritance:
    • Public members of the base class remain public in the derived class.
    • Protected members of the base class remain protected in the derived class.
    • Private members of the base class are not inherited.

    Syntax:

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

    Example:

    class Dog : public Animal {
    // Dog class inherits Animal’s public and protected members
    };
  2. Protected Inheritance:
    • Public and protected members of the base class become protected in the derived class.
    • Private members are not inherited.

    Syntax:

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

    Example:

    class Dog : protected Animal {
    // Public and protected members of Animal are now protected in Dog
    };
  3. Private Inheritance:
    • Public and protected members of the base class become private in the derived class.
    • Private members are not inherited.

    Syntax:

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

    Example:

    class Dog : private Animal {
    // Public and protected members of Animal are now private in Dog
    };

Example: Protected Inheritance

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
protected:
void sleep() {
cout << “This animal is sleeping.” << endl;
}
};

// Derived class
class Dog : protected Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}

void performActions() {
eat(); // Allowed because it’s protected
sleep(); // Allowed because it’s protected
}
};

int main() {
Dog myDog;
// myDog.eat(); // Error: ‘eat’ is now protected in the derived class
myDog.performActions(); // Calls the protected methods within the class
myDog.bark(); // Calls the public method

return 0;
}

In this example, the eat() and sleep() functions from the Animal base class are inherited as protected members in the Dog derived class. They cannot be accessed directly from outside the class but can be used within member functions of the derived class.

Example: Private Inheritance

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
protected:
void sleep() {
cout << “This animal is sleeping.” << endl;
}
};

// Derived class
class Dog : private Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}

void performActions() {
eat(); // Allowed within the derived class
sleep(); // Allowed within the derived class
}
};

int main() {
Dog myDog;
// myDog.eat(); // Error: ‘eat’ is now private in the derived class
myDog.performActions(); // Calls the private methods within the class
myDog.bark(); // Calls the public method

return 0;
}

In this case, all members of the Animal base class become private in the Dog class. They cannot be accessed directly by objects of the derived class, but they can be accessed by the derived class’s member functions.

4.5 Member Function Overriding in C++

Function overriding occurs when a derived class provides a specific implementation of a function that is already defined in its base class. The overridden function in the derived class must have the same signature (name, parameters, and return type) as the function in the base class. Function overriding is used in polymorphism to define specific behavior for the derived class.

Key aspects of function overriding include:

  • Both the base class and derived class must have a function with the same name and signature.
  • The function in the derived class should be declared with the virtual keyword in the base class (though not mandatory for overriding, it is important for runtime polymorphism).
  • Function overriding allows dynamic (runtime) binding, where the appropriate function is called depending on the object type.

Syntax of Function Overriding

class BaseClass {
public:
virtual void display() {
cout << “Display from BaseClass” << endl;
}
};

class DerivedClass : public BaseClass {
public:
void display() override { // ‘override’ is optional but helps indicate overriding
cout << “Display from DerivedClass” << endl;
}
};

In this example, the function display() is overridden in the derived class.

Example of Function Overriding

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
virtual void sound() {
cout << “This is a generic animal sound.” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
void sound() override {
cout << “The dog barks.” << endl;
}
};

// Another Derived class
class Cat : public Animal {
public:
void sound() override {
cout << “The cat meows.” << endl;
}
};

int main() {
Animal* animal;
Dog dog;
Cat cat;

animal = &dog;
animal->sound(); // Output: The dog barks.

animal = &cat;
animal->sound(); // Output: The cat meows.

return 0;
}

Explanation:

  • Base class (Animal) defines a sound() function.
  • Derived classes (Dog and Cat) override the sound() function to provide specific behaviors.
  • In main(), the function that is invoked depends on the actual type of the object (Dog or Cat), even though the pointer is of type Animal*. This is achieved through runtime polymorphism (dynamic binding).

Key Points of Function Overriding:

  1. Virtual Keyword:
    • The virtual keyword is used in the base class to enable function overriding.
    • When a function is marked as virtual in the base class, the overridden version in the derived class will be called when the function is invoked through a base class pointer.
  2. Override Keyword (Optional):
    • The override keyword in the derived class is optional but useful. It helps catch errors if the derived function is not actually overriding a base class function (for example, due to a typo in the function name or parameter mismatch).
    • It ensures that the base class has a virtual function with the same signature.
  3. Dynamic Binding:
    • Function overriding enables dynamic binding or runtime polymorphism. When a base class pointer points to a derived class object, the correct function for the actual object type is called.
  4. Static Binding vs. Dynamic Binding:
    • Without the virtual keyword, C++ uses static binding (compile-time binding), where the function call is resolved based on the type of the pointer or reference, not the actual object type. Using virtual enables dynamic binding (runtime binding).

Example Without Virtual (Static Binding)

#include <iostream>
using namespace std;

class Animal {
public:
void sound() {
cout << “This is a generic animal sound.” << endl;
}
};

class Dog : public Animal {
public:
void sound() { // Not virtual
cout << “The dog barks.” << endl;
}
};

int main() {
Animal* animal = new Dog();
animal->sound(); // Output: This is a generic animal sound (static binding)

delete animal;
return 0;
}

In this example:

  • The sound() function in the base class (Animal) is not virtual.
  • As a result, when animal->sound() is called, the base class version of the function is executed, even though the object is of type Dog.

Pure Virtual Functions (Abstract Classes)

If a function in the base class is pure virtual, it forces derived classes to provide their own implementation of the function. A class with at least one pure virtual function becomes an abstract class and cannot be instantiated.

Syntax of a pure virtual function:

class BaseClass {
public:
virtual void display() = 0; // Pure virtual function
};

Example of Pure Virtual Function

#include <iostream>
using namespace std;

// Abstract base class
class Animal {
public:
virtual void sound() = 0; // Pure virtual function
};

// Derived class
class Dog : public Animal {
public:
void sound() override {
cout << “The dog barks.” << endl;
}
};

// Another Derived class
class Cat : public Animal {
public:
void sound() override {
cout << “The cat meows.” << endl;
}
};

int main() {
Animal* animal;
Dog dog;
Cat cat;

animal = &dog;
animal->sound(); // Output: The dog barks.

animal = &cat;
animal->sound(); // Output: The cat meows.

return 0;
}

In this example:

  • The sound() function in the base class Animal is declared as a pure virtual function, making Animal an abstract class.
  • The derived classes Dog and Cat must override the pure virtual function; otherwise, they would also be abstract classes.

 

4.6 Single, Multiple, multilevel and hybrid Inheritance

In C++, inheritance allows one class (the derived class) to inherit properties and behaviors (data members and member functions) from another class (the base class). This promotes code reuse and enables polymorphism. There are several types of inheritance in C++, each defining how classes inherit from one or more base classes.

1. Single Inheritance

In single inheritance, a derived class inherits from only one base class. This is the simplest form of inheritance.

Syntax:

class BaseClass {
// Base class members
};

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

Example:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Derived class
class Dog : public Animal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}
};

int main() {
Dog dog;
dog.eat(); // Inherited from Animal class
dog.bark(); // Specific to Dog class
return 0;
}

2. Multiple Inheritance

In multiple inheritance, a derived class inherits from more than one base class. The derived class can access members of all its base classes.

Syntax:

class BaseClass1 {
// Base class 1 members
};

class BaseClass2 {
// Base class 2 members
};

class DerivedClass : public BaseClass1, public BaseClass2 {
// Derived class members
};

Example:

#include <iostream>
using namespace std;

// Base class 1
class Person {
public:
void walk() {
cout << “The person is walking.” << endl;
}
};

// Base class 2
class Employee {
public:
void work() {
cout << “The employee is working.” << endl;
}
};

// Derived class
class Manager : public Person, public Employee {
public:
void manage() {
cout << “The manager is managing the team.” << endl;
}
};

int main() {
Manager manager;
manager.walk(); // Inherited from Person
manager.work(); // Inherited from Employee
manager.manage(); // Specific to Manager
return 0;
}

3. Multilevel Inheritance

In multilevel inheritance, a class is derived from a class that is also derived from another class, forming a chain of inheritance.

Syntax:

class BaseClass {
// Base class members
};

class IntermediateClass : public BaseClass {
// Intermediate class members
};

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

Example:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Intermediate class
class Mammal : public Animal {
public:
void giveBirth() {
cout << “This mammal gives birth to live young.” << endl;
}
};

// Derived class
class Dog : public Mammal {
public:
void bark() {
cout << “The dog is barking.” << endl;
}
};

int main() {
Dog dog;
dog.eat(); // Inherited from Animal
dog.giveBirth(); // Inherited from Mammal
dog.bark(); // Specific to Dog
return 0;
}

4. Hybrid Inheritance

Hybrid inheritance is a combination of two or more types of inheritance. For instance, it could involve a mix of multiple and multilevel inheritance.

Hybrid inheritance can sometimes lead to ambiguity in C++, particularly when the same base class appears more than once in the inheritance hierarchy. To resolve this, virtual inheritance is often used.

Example:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Intermediate class 1
class Mammal : public Animal {
public:
void giveBirth() {
cout << “This mammal gives birth to live young.” << endl;
}
};

// Intermediate class 2
class Bird : public Animal {
public:
void layEggs() {
cout << “This bird lays eggs.” << endl;
}
};

// Derived class
class Bat : public Mammal, public Bird {
public:
void fly() {
cout << “The bat is flying.” << endl;
}
};

int main() {
Bat bat;
bat.eat(); // Error: Ambiguity between Mammal’s and Bird’s version of eat()
bat.giveBirth();
bat.layEggs();
bat.fly();
return 0;
}

In the example above, Bat inherits from both Mammal and Bird, which in turn inherit from Animal. This leads to ambiguity for the eat() function, because Bat inherits it from both Mammal and Bird. To solve this, virtual inheritance is used.

Using Virtual Inheritance to Resolve Ambiguity:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
void eat() {
cout << “This animal is eating.” << endl;
}
};

// Intermediate class 1
class Mammal : virtual public Animal {
public:
void giveBirth() {
cout << “This mammal gives birth to live young.” << endl;
}
};

// Intermediate class 2
class Bird : virtual public Animal {
public:
void layEggs() {
cout << “This bird lays eggs.” << endl;
}
};

// Derived class
class Bat : public Mammal, public Bird {
public:
void fly() {
cout << “The bat is flying.” << endl;
}
};

int main() {
Bat bat;
bat.eat(); // No ambiguity, as Animal is virtually inherited
bat.giveBirth(); // Inherited from Mammal
bat.layEggs(); // Inherited from Bird
bat.fly(); // Specific to Bat
return 0;
}

In this corrected example, Mammal and Bird inherit from Animal using virtual inheritance, which resolves the ambiguity for the eat() function.

 

4.7 Ambiguity problems in inheritance

In object-oriented programming, ambiguity occurs when there is confusion over which method or property to inherit in cases of multiple inheritance. This situation often arises when a derived class inherits from two or more base classes that have methods or variables with the same name. Ambiguity issues commonly occur in multiple inheritance and hybrid inheritance.

Ambiguity in Multiple Inheritance

When a derived class inherits from multiple base classes, and these base classes have members (methods or variables) with the same name, the compiler becomes confused about which one to use. This leads to an ambiguity problem.

Example of Ambiguity in Multiple Inheritance:

#include <iostream>
using namespace std;

// Base class 1
class A {
public:
void show() {
cout << “Show function in class A” << endl;
}
};

// Base class 2
class B {
public:
void show() {
cout << “Show function in class B” << endl;
}
};

// Derived class
class C : public A, public B {
};

int main() {
C obj;
// obj.show(); // This will cause ambiguity because both A and B have show() method.
return 0;
}

In this example, class C inherits from both class A and class B, both of which have a show() function. When trying to call obj.show(), the compiler will not know which show() method to invoke, causing an ambiguity error.

Resolving Ambiguity using Scope Resolution Operator:

To resolve the ambiguity, the scope resolution operator (::) can be used to specify which base class’s function to invoke.

#include <iostream>
using namespace std;

// Base class 1
class A {
public:
void show() {
cout << “Show function in class A” << endl;
}
};

// Base class 2
class B {
public:
void show() {
cout << “Show function in class B” << endl;
}
};

// Derived class
class C : public A, public B {
};

int main() {
C obj;
obj.A::show(); // Resolves ambiguity by explicitly calling show() from class A
obj.B::show(); // Resolves ambiguity by explicitly calling show() from class B
return 0;
}

Here, obj.A::show() calls the show() function from class A, and obj.B::show() calls the show() function from class B, thus resolving the ambiguity.


Ambiguity in Hybrid Inheritance

In hybrid inheritance, ambiguity occurs when a derived class inherits from multiple base classes, and those base classes themselves inherit from a common base class, creating an inheritance diamond. This is known as the Diamond Problem.

Example of Ambiguity in Hybrid Inheritance (Diamond Problem):

#include <iostream>
using namespace std;

// Base class
class A {
public:
void show() {
cout << “Show function in class A” << endl;
}
};

// Derived class 1
class B : public A {
};

// Derived class 2
class C : public A {
};

// Final derived class
class D : public B, public C {
};

int main() {
D obj;
// obj.show(); // This will cause ambiguity because class D has two A base classes.
return 0;
}

In this example, class D inherits from both class B and class C, which both inherit from class A. When trying to call obj.show(), the compiler cannot decide which version of show() from A to use, leading to ambiguity.

Resolving Ambiguity using Virtual Inheritance:

To avoid the ambiguity in this scenario, virtual inheritance can be used. Virtual inheritance ensures that only one instance of the common base class (A in this case) is inherited by the derived classes (B and C), preventing the duplication of the base class.

#include <iostream>
using namespace std;

// Base class
class A {
public:
void show() {
cout << “Show function in class A” << endl;
}
};

// Derived class 1
class B : virtual public A {
};

// Derived class 2
class C : virtual public A {
};

// Final derived class
class D : public B, public C {
};

int main() {
D obj;
obj.show(); // No ambiguity, as only one instance of class A is inherited.
return 0;
}

By making the inheritance from A virtual, we ensure that only one copy of A exists in D, thus resolving the ambiguity.

 

4.8 Constructors in Derived Class

In object-oriented programming, particularly in C++, constructors are special member functions that are automatically called when an object of a class is created. When dealing with inheritance, constructors play a crucial role in initializing base class parts of derived class objects.

Key Points about Constructors in Derived Classes:

  1. Initialization Order:
    • When a derived class object is created, the constructor of the base class is invoked before the constructor of the derived class. This ensures that the base part of the derived object is fully initialized before any derived-specific initialization occurs.
  2. Constructor Initialization List:
    • The derived class constructor can use an initialization list to call a specific base class constructor. This is particularly useful when the base class has constructors that take parameters.
  3. Default Constructors:
    • If the base class does not have a default constructor (i.e., a constructor that takes no arguments), the derived class must explicitly call one of the base class constructors using the initialization list.

Example of Constructors in Derived Classes

Here’s a simple example to illustrate how constructors work in derived classes:

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
Animal() { // Default constructor
cout << “Animal constructor called.” << endl;
}

Animal(string name) { // Parameterized constructor
cout << “Animal constructor called for: ” << name << endl;
}
};

// Derived class
class Dog : public Animal {
public:
Dog() : Animal(“Dog”) { // Calling the parameterized base class constructor
cout << “Dog constructor called.” << endl;
}
};

int main() {
Dog dog; // Creating an object of the derived class
return 0;
}

Output:

Animal constructor called for: Dog
Dog constructor called.

Explanation of the Example:

  1. Base Class Constructor:
    • The Animal class has both a default constructor and a parameterized constructor. The parameterized constructor takes a string argument to represent the name of the animal.
  2. Derived Class Constructor:
    • The Dog class derives from Animal. In the constructor of Dog, the parameterized constructor of Animal is explicitly called using the initialization list: : Animal("Dog"). This means that when a Dog object is created, it will first invoke the Animal constructor that takes a string.
  3. Order of Execution:
    • When Dog dog; is executed in the main function, the following happens:
      • First, the Animal constructor is called, outputting Animal constructor called for: Dog.
      • Next, the Dog constructor is called, outputting Dog constructor called.

Using Initialization Lists for Base Class Constructors

If you have a base class with constructors that require parameters, you must use the initialization list in the derived class constructor to pass the required arguments to the base class constructor. Here’s an example:

ing namespace std;

// Base class
class Vehicle {
public:
Vehicle(int wheels) {
cout << “Vehicle with ” << whe#include <iostream>

usels << ” wheels created.” << endl;
}
};

// Derived class
class Car : public Vehicle {
public:
Car() : Vehicle(4) { // Calling base class constructor with 4 wheels
cout << “Car created.” << endl;
}
};

int main() {
Car myCar; // Creating an object of Car
return 0;
}

Output:

Vehicle with 4 wheels created.
Car created.

4.9 Extending operator overloading in derived class

Operator overloading allows developers to redefine the behavior of operators (like +, -, *, etc.) for user-defined types (classes). When dealing with inheritance, you can extend operator overloading to derived classes, allowing them to inherit or override the behavior of the operators defined in their base classes.

Key Points:

  1. Inheriting Operator Overloads:
    • When a derived class inherits from a base class that has overloaded operators, those operators are available in the derived class unless they are explicitly overridden.
  2. Overriding Operators in Derived Classes:
    • You can provide a different implementation for an operator in a derived class if the behavior needs to be different from that of the base class.
  3. Using Base Class Operators:
    • The derived class can use the base class’s operator implementations directly if they haven’t been overridden.

Example: Extending Operator Overloading in Derived Class

Here’s a simple example to illustrate how operator overloading can be extended in a derived class.

#include <iostream>
using namespace std;

// Base class
class Base {
public:
int value;

Base(int v) : value(v) {}

// Overloading the ‘+’ operator
Base operator+(const Base &other) {
return Base(this->value + other.value);
}

// Display the value
void display() {
cout << “Base Value: ” << value << endl;
}
};

// Derived class
class Derived : public Base {
public:
Derived(int v) : Base(v) {}

// Overloading the ‘+’ operator in derived class
Base operator+(const Derived &other) {
return Base(this->value * other.value); // Different behavior: product instead of sum
}
};

int main() {
Base b1(10), b2(20);
Base b3 = b1 + b2; // Using Base class operator+
b3.display(); // Output: Base Value: 30

Derived d1(2), d2(3);
Base d3 = d1 + d2; // Using Derived class operator+
d3.display(); // Output: Base Value: 6

return 0;
}

Output:

Base Value: 30
Base Value: 6

Explanation of the Example:

  1. Base Class:
    • The Base class contains a constructor that initializes the value attribute and overloads the + operator to add the values of two Base objects.
  2. Derived Class:
    • The Derived class inherits from Base and has its own + operator overload. However, instead of adding the values, it multiplies them.
  3. Usage:
    • In the main function, two Base objects (b1 and b2) are created, and their + operator is used, resulting in b3 having a value of 30.
    • Then, two Derived objects (d1 and d2) are created. The overloaded + operator in the Derived class is invoked, resulting in d3 having a value of 6 (2 * 3).

 

Important Questions
Comments
Discussion
0 Comments
  Loading . . .