πŸ§‘β€πŸŽ“Inheritance and Polymorphism

Inheritance is the capability of one class to acquire the properties of another. It allows the developer to reuse the code by creating a new class that inherits the functionality of an existing class. It serves as a mechanism for code re-usability.

Polymorphism means the ability of an object to take many forms. The most common use of polymorphism in C++ occurs when a parent class reference is used to refer to objects of a child class. When a function or operator is called using a parent class reference, it is the version of the function or operator defined in the child class that is called, thus allowing the child class object to have polymorphic behavior.

Inheritance Concept

Inheritance is an important concept in OOP which allows us to define a class in terms of another class, thereby providing code reusability. In inheritance, we divide the program into a hierarchical order of classes.

The main idea behind inheritance is that you define a class which inherits the properties and behaviors of another class. The class whose properties are inherited is called the base class or parent class. The class that inherits the other class is called the derived class or child class. This allows code reusability and prevents the repetition of code.

In C++, inheritance is achieved through the use of the colon (:) symbol. The syntax is:

class derived-class: access-specifier base-class
{
// body of derived class
}

The derived class inherits all the members (properties and behaviors) of the base class except the constructors and destructors. The derived class can add its own fields and methods and can also override the base class methods.

Inheritance provides the reusability of a code and provides a security as the base class data can only be accessed through derived class functions. It also makes the program modular by dividing it into manageable parts. Inheritance provides an is-a relationship between classes.

Hope this helps explain the concept of inheritance in CPP! Let me know if you have any other questions.

Inheritance in C++

Here are the main concepts regarding inheritance in C++

Class Hierarchy: Inheritance establishes a class hierarchy between base and derived classes. The base class sits at the top of the hierarchy and derived classes inherit from the base class.

Types of Inheritance:

  1. Single Inheritance: A derived class inherits from only one base class.

  2. Multilevel Inheritance: A derived class inherits from another derived class. This forms a chain of classes.

  3. Hierarchical Inheritance: A base class is inherited by multiple derived classes.

  4. Multiple Inheritance: A derived class inherits from more than one base class.

Access Specifiers: We use access specifiers to specify the accessibility of the members of the base class in the derived class. The access specifiers are:

  • public: Members are accessible everywhere

  • protected: Members are accessible within the class and its derived classes

  • private: Members are accessible only within the class

Inheritance Features:

  • Code reusability

  • Type safety (inherited members can only be accessed through objects of the derived class)

  • Support for IS-A relationship between classes

  • Compile time binding (binding happens at compile time)

Hope this summary helps explain inheritance in C++! Let me know if you have any other questions.

Protected Members

Protected members in C++ are members of a class that can only be accessed by:

  • The class itself

  • Classes derived from it

  • Friends of the class

Protected members allow you to hide implementation details within a class hierarchy, while still allowing derived classes to access those members.

The main benefits of protected members are:

  1. Encapsulation: Protected members encapsulate data within a class and its derived classes, hiding the implementation from the outside world. This results in more maintainable and robust code.

  2. Controlled accessibility: By making members protected, we control which classes can access and modify those members.

  3. Reusability: Derived classes can reuse the protected members of the base class.

The syntax for a protected member is:

protected:
type member1;
type member2;
...

Only classes derived from the class containing the protected members, and the class itself, can access protected members.

For example:

class Base
{
protected:
int a;
};
class Derived: public Base
{
void func()
{
a = 10; // OK, can access 'a'
}
};

So in summary, protected members allow us to share data within a class hierarchy, while hiding implementation details from outside classes.

Hope this explanation helps! Let me know if you have any other questions.

Base Class Initializer List

Here is an example of using a base class initializer list in C++ :

#include <iostream>
using namespace std;

class Base {
public: 
    int a, b;
    Base(int x, int y) : a(x), b(y) { } 
};

class Derived : public Base  {
public:
    int c; 
    Derived(int x, int y, int z) : Base(x, y), c(z) { } 
};

int main() {
    Derived d(1, 2, 3); 
    cout << d.a << " " << d.b << " "<< d.c; 
    // Prints 1 2 3
}

Here we have a base class Base and a derived class Derived. In the constructor of Derived, we use a base class initializer list to initialize the base class Base as:

Derived(int x, int y, int z) : Base(x, y), c(z)

This initializes the base class Base with the arguments (x, y) and then initializes the member c of the derived class.

The benefits of using a base class initializer list are:

  1. Efficiency - It initializes base class members before the constructor body is executed.

  2. Correctness - It ensures members of base classes are correctly initialized before the constructor body.

  3. Eliminates ambiguity - Without an initializer list, the compiler would not know whether to call the base class default constructor or a derived class constructor.

So in summary, base class initializer lists allow you to initialize base class members before the constructor body is executed, ensuring correct initialization of base class members.

Hope this example helps explain base class initializer lists! Let me know if you have any other questions.

Composition

Here is an example of composition in C++ :

#include <iostream> 
using namespace std;

class Engine {
public:
    void start() { cout << "Engine started" << endl; }
    void stop() { cout << "Engine stopped" << endl; }  
};

class Car {  
private:
    Engine engine; // Composition 
public:
    void drive() {
        engine.start(); 
        cout << "Driving..." << endl; 
    }  
};

int main() {
    Car c; 
    c.drive(); 
    c.engine.stop();
}

Output:

Engine started
Driving...
Engine stopped

In this example, we have two classes:

  • Engine : Represents the car engine

  • Car: Represents the car itself

The Car class has an Engine object as a member variable. This denotes a composition relationship - a Car "has-a" Engine.

The benefits of composition over inheritance are:

  1. Maintainability - Compositions are usually more robust to changes than inheritances.

  2. Reusability - Composed objects can be reused in different contexts.

  3. Flexibility - We can change the composed object at runtime.

  4. Modularity - It divides a system into independent modules.

In summary, composition is a type of association between classes where one class owns another class. The composed class delegates work to the component class, allowing for high cohesion and loose coupling.

Hope this example helps explain composition! Let me know if you have any other questions.

Member Initialization List

Here is an example of using a member initialization list in C++ :

#include <iostream>
using namespace std;

class Point {
private:
    int x, y;  
public:
    Point(int x, int y) : x(x), y(y) { } 
    
    void print() {
        cout << x << ", " << y; 
    }
};

int main() {
    Point p(1,2);
    p.print();
}

Output:

1, 2

In the Point constructor, we use a member initialization list to initialize the member variables x and y:

Point(int x, int y) : x(x), y(y)

This initializes x with the argument x and y with the argument y.

The benefits of using a member initialization list are:

  1. Efficiency - It initializes members before the constructor body is executed.

  2. Correctness - It ensures members are initialized with the correct values passed as arguments.

  3. Eliminates ambiguity - Without an initialization list, the compiler would initialize members after the constructor body, potentially overriding any initialization done in the body.

In summary, member initialization lists allow you to initialize member variables before the constructor body is executed, ensuring they are initialized with the intended values.

Hope this helps explain member initialization lists! Let me know if you have any other questions regarding C++ programming.

Order of Initialization

The order of initialization in C++ is:

  1. Base class constructors are called

  2. Data members are initialized via initialization lists

  3. Derived class constructors are called

  4. Member functions are executed

For example:

#include <iostream>
using namespace std;

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

class Derived: public Base  {  
int a;
public:
    Derived(): a(5){ cout << "Derived constructor\n"; }
    void print() { cout << "a = " << a; }
};

int main() {
    Derived d;
    d.print();  
}

Output:

Base constructor
Derived constructor
a = 5

Here we can see:

  1. Base class constructor is called first

  2. Then the member a is initialized via initialization list

  3. Then the derived class constructor body is executed

  4. Finally, the member function print() is called

The order of initialization ensures that:

  • Base class members are initialized before derived class members

  • Members are initialized before they are used

  • Constructors are called in the proper order from base to derived classes.

Hope this helps explain the order of initialization in C++! Let me know if you have any other questions.

Inheritance vs. Composition

Here is example code demonstrating the difference between inheritance and composition in C++:

Inheritance:

class Vehicle { 
public:
    Vehicle() { cout << "Vehicle created" << endl; }
};

class Car: public Vehicle {
public:
    Car() { cout << "Car created" << endl; }  
};

int main() {
    Car c;
}

Output:

Vehicle created
Car created

Here the Car class inherits from the Vehicle class, forming an "is-a" relationship.

Composition:

class Vehicle {  
public:
    Vehicle() { cout << "Vehicle created" << endl; }  
};

class Car {
private:
    Vehicle vehicle;  
public:
    Car() { cout << "Car created" << endl; }
};

int main() {
    Car c;
}

Output:

Vehicle created
Car created

Here the Car class composes a Vehicle object, forming a "has-a" relationship.

Key differences:

Inheritance:

  • Creates an "is-a" relationship

  • Members of the base class become members of derived class

  • Child class relies on parent class

Composition:

  • Creates a "has-a" relationship

  • Child class has a reference to parent class

  • Child class is independent of parent class

Hope this example code helps illustrate the difference between inheritance and composition in C++. Let me know if you have any other questions!

A Case for Polymorphism

Here is an example demonstrating the need for polymorphism in C++:

class Shape {
public:
    virtual void draw() = 0;
};

class Circle: public Shape {
public:
    void draw() override {
        cout << "Drawing a circle" << endl; 
    }
};

class Rectangle: public Shape {
public:  
    void draw() override {
        cout << "Drawing a rectangle" << endl;      
    }
};

Now say we have a function that needs to draw a shape:

void draw_shape(Shape *s) {
    s->draw(); 
}

We can call it like this:

Circle c;
draw_shape(&c);

Rectangle r;
draw_shape(&r);

This will print:

Drawing a circle
Drawing a rectangle

So by passing a base class pointer but calling the derived class draw() method, we achieved polymorphism - letting the object decide which draw() method to call at runtime.

The benefits of polymorphism are:

  1. Code reusability - We can call the same function draw_shape() for different shapes.

  2. Loose coupling - The draw_shape() function does not need to know the concrete shape class.

  3. Extensibility - We can add new shape classes and call draw_shape() without modifying existing code.

So in summary, polymorphism allows us to call different implementations of the same method, based on the type of object at runtime. This makes our code reusable, extensible and decoupled.

Hope this example demonstrates the need for polymorphism! Let me know if you have any other questions regarding C++ programming.

Dynamic Binding

#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing Shape" << endl;
}
};
class Rectangle: public Shape {
public:
void draw() {
cout << "Drawing Rectangle" << endl;
}
};
class Circle: public Shape {
public:
void draw() {
cout << "Drawing Circle" << endl;
}
};
int main(){
Shape *s;
s = new Rectangle();
s->draw();
s = new Circle();
s->draw();
return 0;
}

//Output:

Drawing Rectangle
Drawing Circle

This example demonstrates dynamic binding in C++. The base class Shape has a virtual function draw(). When a derived class object is assigned to the base class pointer, the correct overridden draw() function is called at runtime, based on the actual type of the object.

Pointer Conversion in Inheritance

Here is an example of pointer conversion in inheritance in C++ :

#include <iostream>

using namespace std;

class Shape {
  public: virtual void draw() {
    cout << "Shape draw" << endl;
  }
};

class Rectangle: public Shape {
  public: void draw() {
    cout << "Rectangle draw" << endl;
  }
};

int main() {
  Shape * base = new Rectangle();

  base -> draw(); // Calls Rectangle::draw()

  Rectangle derived = dynamic_cast < Rectangle > (base);

  if (derived != 0) {
    derived -> draw(); // Also calls Rectangle::draw()
  }
  return 0;
}

Output:

Rectangle draw
Rectangle draw

Here we have:

  • base pointer of type Shape * points to a Rectangle object

  • This performs pointer conversion

  • When we call base->draw(), the overridden Rectangle ::draw() is called

  • We then cast base to Rectangle * using dynamic_cast

  • If the cast is valid, derived will point to the Rectangle object

  • We call derived->draw() which again calls Rectangle ::draw()

So pointer conversion allows us to access derived class functions using a base class pointer. dynamic_cast performs a safe runtime cast.

Hope this helps! Let me know if you have any other questions

Polymorphism Using Dynamic Binding

// Some code
#include <iostream>

using namespace std;

class Shape {
  public: virtual void draw() {
    cout << "Drawing Shape" << endl;
  }
};

class Rectangle: public Shape {
  public: void draw() {
    cout << "Drawing Rectangle" << endl;
  }
};

class Circle: public Shape {
  public: void draw() {
    cout << "Drawing Circle" << endl;
  }
};

int main() {
  Shape * s;
  Rectangle r;
  s = & r;
  s -> draw(); // Calls Rectangle::draw()

  Circle c;
  s = & c;
  s -> draw(); // Calls Circle::draw()    

  return 0;
}

Output:

Drawing Rectangle
Drawing Circle

This demonstrates polymorphism using dynamic binding in C++.

We have:

  • A base class Shape with a virtual function draw()

  • Derived classes Rectangle and Circle override draw()

  • A base class pointer s that points to Rectangle and Circle objects

  • When we call s->draw(), the correct overridden draw() is called based on the actual object type - this is dynamic binding.

So at compile time, the compiler sees a call to Shape::draw() But at runtime, the function call is resolved to the appropriate overridden draw() based on the object type - Rectangle::draw() or Circle::draw().

Virtual Function Specification

In C++, virtual functions are functions that can be overridden by derived classes. They are declared using the virtual keyword in the base class, and the override keyword in the derived class.

Here is an example code that demonstrates the use of virtual functions:

#include <iostream>

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal speaks!" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "Meow!" << std::endl;
    }
};

int main() {
    Animal* animal = new Animal();
    animal->speak(); // prints "Animal speaks!"

    Animal* dog = new Dog();
    dog->speak(); // prints "Woof!"

    Animal* cat = new Cat();
    cat->speak(); // prints "Meow!"

    delete animal;
    delete dog;
    delete cat;

    return 0;
}

In this example, we have a base class Animal with a virtual function speak(), which is overridden by the derived classes Dog and Cat. We create instances of each class and call their speak() function, which produces different output depending on the class type.

Note that we use pointers to Animal objects to create instances of Dog and Cat. This is because virtual functions work with pointers and references to objects, not with objects themselves.

Invoking Virtual Functions

In C++, virtual functions can be invoked through a pointer or reference to an object. When a virtual function is called through a pointer or reference, the version of the function that is called depends on the type of the actual object pointed to or referred to. Here is an example code that demonstrates how to invoke virtual functions:

#include <iostream>

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal speaks!" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "Meow!" << std::endl;
    }
};

int main() {
    Animal* animal = new Animal();
    animal->speak(); // prints "Animal speaks!"

    Dog dog;
    Animal& animalRef = dog;
    animalRef.speak(); // prints "Woof!"

    Cat cat;
    Animal* animalPtr = &cat;
    animalPtr->speak(); // prints "Meow!"

    delete animal;

    return 0;
}

In this example, we create an instance of the Animal class and call its speak() function. This produces the output "Animal speaks!". We then create instances of the Dog and Cat classes and call their speak() functions through a reference and a pointer to Animal, respectively. This produces the output "Woof!" and "Meow!", respectively. The version of the speak() function that is called depends on the type of the object that the pointer or reference points to or refers to.

VTable in C++

In C++, a virtual table (vtable) is a mechanism used to implement dynamic dispatch of virtual functions. It is a table of function pointers that is created by the compiler for each class that has virtual functions, and it is used to resolve which version of a virtual function should be called at runtime.

Each object of a class with virtual functions has a hidden pointer, called the vpointer or vptr, that points to the vtable of the class. When a virtual function is called through an object pointer or reference, the vptr is used to locate the correct entry in the vtable, which contains a pointer to the correct version of the function.

Here is an example code that demonstrates how a vtable is used to implement dynamic dispatch:

#include <iostream>

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal speaks!" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "Meow!" << std::endl;
    }
};

int main() {
    Animal* animal = new Animal();
    animal->speak(); // prints "Animal speaks!"

    Dog dog;
    Animal& animalRef = dog;
    animalRef.speak(); // prints "Woof!"

    Cat cat;
    Animal* animalPtr = &cat;
    animalPtr->speak(); // prints "Meow!"

    delete animal;

    return 0;
}

In this example, the vtable for each class with virtual functions is created by the compiler, and each object of these classes has a vptr that points to its vtable. When a virtual function is called through an object pointer or reference, the vptr is used to locate the correct entry in the vtable, which contains a pointer to the correct version of the function. This allows the correct version of the function to be called at runtime, depending on the actual type of the object pointed to or referred to.

Virtual Destructors

In C++, a virtual destructor is a destructor that is declared as virtual in a base class. When a class with virtual functions is used as a base class, it is recommended to make the destructor virtual to ensure that the destructors of all derived classes are called properly.

Here is an example code that demonstrates the use of virtual destructors:

#include <iostream>

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

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

int main() {
    Animal* animal = new Dog();
    delete animal;

    return 0;
}

In this example, we define two classes: Animal and Dog. The Animal class has a virtual destructor, which is declared using the virtual keyword. The Dog class has a non-virtual destructor.

In the main() function, we create an object of the Dog class and assign it to a pointer of the Animal class. We then delete the object using the delete operator. Since the Animal class's destructor is declared as virtual, the destructor of the Dog class is called properly, even though the object is being deleted through a pointer of the base class.

The output of this program is:

Animal constructor
Dog constructor
Dog destructor
Animal destructor

This shows that the destructors of both the base class and the derived class are called in the correct order, with the derived class's destructor being called first.

Abstract Class Using Pure Virtual Function

In C++, an abstract class is a class that has at least one pure virtual function, which is a virtual function that is declared using the = 0 syntax. An abstract class cannot be instantiated, and it can only be used as a base class for other classes.

Here is an example code that demonstrates the use of an abstract class with a pure virtual function:

#include <iostream>

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

class Rectangle : public Shape {
public:
    Rectangle(double width, double height) : width_(width), height_(height) {}
    double area() const override {
        return width_ * height_;
    }
private:
    double width_, height_;
};

class Circle : public Shape {
public:
    Circle(double radius) : radius_(radius) {}
    double area() const override {
        return 3.14 * radius_ * radius_;
    }
private:
    double radius_;
};

int main() {
    // Shape* shape = new Shape(); // this will not compile, since Shape is an abstract class

    Rectangle rectangle(5, 10);
    std::cout << "Rectangle area: " << rectangle.area() << std::endl;

    Circle circle(5);
    std::cout << "Circle area: " << circle.area() << std::endl;

    return 0;
}

In this example, we define an abstract class Shape with a pure virtual function area(). This function is declared with the = 0 syntax, which makes Shape an abstract class that cannot be instantiated.

We then define two classes that derive from Shape: Rectangle and Circle. Both of these classes implement the area() function, which is required since it is a pure virtual function in the Shape class.

In the main() function, we create instances of Rectangle and Circle and call their area() functions to calculate their areas.

Note that attempting to create an instance of the abstract class Shape using the new operator will result in a compilation error.

Last updated