Search code examples
c++inheritanceoverridingvirtual-functions

Overriding virtual functions and inheritance


I'm having trouble fully understanding overriding virtual functions in C++ and what exactly happens when such functions are called. I'm reading PPP using C++ by Bjarne Stroustrup and he provides the following example to show overriding and virtual functions:

struct B {
    virtual void f() const { cout << "B::f"; }
    void g() const { cout << "B::g"; }        //not virtual
};

struct D : B {
        void f() const { cout << "D::f"; } //overrides B::f
        void g() { cout << "D::g"; }
};

struct DD : D {
        void f() { cout << "DD::f"; }
        void g() const { cout << "DD::g"; }    
};

void call(const B& b) {
    // a D is kind of B, so call() can accept D
    // a DD is kind of D and a D is a kind of B, so call() can accept a DD
    b.f();
    b.g();
}

int main() {
    B b;
    D d;
    DD dd;

    call(b);
    call(d);
    call(dd);

    b.f();
    b.g();
    d.f();
    d.g();
    dd.f();
    dd.g();
}

Output: B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g

I understand how call(b) outputs B::f B::g, that is straightforward.

Now call(d). I don't quite understand why but it seems that call() can take derived classes of B as an argument. Ok. So in call(), b.f() becomes d.f(), since D::f overrides B::f . Indeed, output says D::f. But D::g doesn't override B::g and for reasons I can't understand it seems to me that D::g is having no effect when call(d) is executed – outputs B::g in this case.

Next, we execute call(dd) which outputs D::f B::g. Applying the same logic(?) as above it's clear that DD::f doesn't override D::f – not const – and DD::g doesn't override neither D::g nor B::g since neither are virtual.

What happens next bewilders me. Each individual call of b.f() , b.g() , d.f(), d.g(), dd.f(), dd.g() outputs result as if overriding didn't exist at all! For example, how could d.g() output D::g when just seconds ago d.g() in call() output B::g ? Another, how could dd.f() output DD::f when in dd.f() in call() output D::f ?

It's safe to say I'm missing something big here, and for that I need help.


Solution

  • Please be patient and read this to get answers to your questions.

    Inheritance is a concept in which object of a class inherits the properties and behaviour of object of another class.

    Basic Introduction

    • Parent class is also called as base class or super class.
    • Child class is also called as derived class or subclass.
    • An object of a derived class can be referenced through the base class.

    For example

    #include <iostream>
    using namespace std;
    class Base {};
    class Derived : public Base {};
    int main() {
      Base *b = new Derived(); // This is completely valid
      return 0;
    }
    

    Method Overriding in C++

    Let's take a basic example

    #include <iostream>
    using namespace std;
    class Base {
    public:
      void display() { cout << "Base display called\n"; }
    };
    class Derived : public Base {
    public:
      void display() { cout << "Derived display called\n"; }
    };
    int main() {
      Base b;
      b.display();
      Derived d;
      d.display();
      Base *bptr = &d;
      bptr->display();
      return 0;
    }
    

    Output:

    Base display called
    Derived display called
    Base display called
    

    Now, from the above example you might have guessed that the derived class overrides the base class but this is not the case. The output (3rd line) shows that the base class function is called because the function is not virtual.

    Virtual functions in C++

    You can make any function of the class virtual by adding the "virtual" keyword at the start of the function.

    Lets consider the virtual function example

    #include <iostream>
    using namespace std;
    class Base {
    public:
      virtual void display() { cout << "Base display called\n"; }
    };
    class Derived : public Base {
    public:
      void display() { cout << "Derived display called\n"; }
    };
    int main() {
      Base b;
      b.display();
      Derived d;
      d.display();
      Base *bptr = &d;
      bptr->display();
      return 0;
    }
    

    Output:

    Base display called
    Derived display called
    Derived display called
    

    By the above example(line 3 of the output), it is clear that you can achieve method overriding by using the virtual function mechanism in C++.

    What is the effect of making a function virtual?

    The difference between a normal function and a virtual function is that a normal function is resolved at compile time, also known as static binding, whereas the virtual function is resolved at run time, also known as dynamic binding or late binding. Which method to call (base class display or derived class display method) is resolved at run time since the base class display function is made virtual. You can dive deep into the virtual function mechanism by reading about v-table.

    Answers to questions

    1. call(d). I don't quite understand why but it seems that call() can take derived classes of B as an argument.

      • A derived class object can be referenced by a base class
    2. But D::g doesn't override B::g and for the reasons I can't understand.

      • Because g() is not virtual in base class. Only virtual functions can be overridden. v-table has entries to only virtual functions so that they can be overridden at run time.
    3. call(dd)

      • Since f() in DD is a non-const function, therefore f() (non-const f() in DD) is not an overridden method of the parent class D. And since it is being referenced by Base class B, calling b.f() would call the const f() which is being overridden by D. Thus D::f gets printed.
      • If f() was a const method in DD then the following would have happened:
        When f() gets called, the function is searched first in the base class B, since f() is virtual, using the v-table pointer, the f() function being overridden in derived class D is resolved. But since f() is not virtual in class D, the overridden f() in DD can't be resolved. Hence D::f gets printed.
        But for g(), the base class itself doesn't have virtual g(), so the overridden functions in their derived classes can't be resolved. Hence, B::g get printed.
    4. The above polymorphism has happened because the derived classes were referenced by their base classes(parent classes), but in the last calls, there is no such thing. All the objects are referenced by their respective classes, and hence their appropriate methods are being called.

    One basic logic to think about this is that the function is first looked up in the referencing class, and if it is virtual then the function will be searched in derived classes(if the object being referred is a child class). If the derived classes override, then the derived method will be called, else the base method will be called. You can further extend the concept applied to the base class, to the derived class as well like the check whether the function is virtual or not and then if the function is virtual and the object is derived(child of the derived, grandchild of the base) and so on.

    Hope this clarifies.