0

Here are my code

    #include <iostream>
    using namespace std;

    class BaseClass
    {
    public:
        BaseClass() {}
        void init(const int object) { cout<<"BaseClass::init"<<endl; }
        void run(const int object) { cout<<"BaseClass::run calls =>";    init(object); }
    };
    
    class Derived : public BaseClass {
    public:
        Derived() {}
        void init(const int object) { cout<<"Derived::init"<<endl; }
    };
    
    int main() {
        BaseClass b;
        b.init('c');
        b.run('c');
    
    
        Derived d;
        d.init(5); // Calls Derived::init
        d.run(5);  // Calls Base::init. **I expected it to call Derived::init**
    }

And here is generated output

BaseClass::init
BaseClass::run calls =>BaseClass::init
Derived::init
BaseClass::run calls =>BaseClass::init

With call d.run(5), Why "BaseClass::init" is being called instead of "Derived::init" ? I though we need virtual functions only when calling through a pointer.

What is the rationale behind keeping such behavior ?

Shakti Malik
  • 2,335
  • 25
  • 32

2 Answers2

1

Why "BaseClass::init" is being called instead of "Derived::init" ?

Because init is a non-virtual member function. To have the desired effect, you need to make init a virtual member function as shown below:

class BaseClass
{
public:
    BaseClass() {}
    //NOTE THE VIRTUAL KEYWORD HERE
    virtual void init(const int object) { cout<<"BaseClass::init"<<endl; }
    //other member function here as before
};

Demo


I though we need virtual functions only when calling through a pointer.

Note that the statement init(object); is equivalent to writing

this->init(object); //here `this` is a pointer 

When you wrote:

d.run(5);

In the above statement, first the address of object d is implicitly passed as the first argument to the implicit this parameter of member function run. The type of this implicit this parameter is BaseClass* and this happens due to derived to base conversion. Now, the call init(object); is equivalent to this->init(object);. But since init is a non-virtual member function, the call is resolved at compile time meaning the base class init will be called.

Basically when a member function is called with a derived class object, the compiler first looks to see if that member exists in the derived class. If not, it begins walking up the inheritance chain and checking whether the member has been defined in any of the parent classes. It uses the first one it finds. This means for the call d.run(5) the search for a member function named run starts inside the derived class. But since there is no function named run inside derived class, the compiler looks into the direct base class BaseClass and finds the member function named run. So it stops its search and uses this found run member function. And as i said, in your example, init is non-virtual and so the call is resolved at compile time to the base class run.

On the other hand, if we make init to be a virtual member function, then this call will be resolved at run-time meaning the derived class init will be called.

Shakti Malik
  • 2,335
  • 25
  • 32
Jason
  • 36,170
  • 5
  • 26
  • 60
  • " The type of this implicit this parameter is BaseClass* and this happens due to derived to base conversion" Why do we require this ? Doesn't Derived class also has run through inheritance ? so why does it require "derived to base conversion" ? – Shakti Malik Mar 07 '22 at 09:50
  • @ShaktiMalik It(derived to base conversion) happens automatically. For example, when you wrote `d.run(5)` the compiler tries to find a member function named `run` inside the derived class but since it can't find one, it looks into the direct base class `BaseClass` where it finds the function named `run` and so it stops looking further. And since it found the function in base class, it will resolve the call `d.run(5);` to this base class function. – Jason Mar 07 '22 at 09:53
  • @ShaktiMalik Basically when a member function is called with a derived class object, the compiler first looks to see if that member exists in the derived class. If not, it begins walking up the inheritance chain and checking whether the member has been defined in any of the parent classes. It uses the first one it finds. – Jason Mar 07 '22 at 10:03
0

I though we need virtual functions only when calling through a pointer.

No. Thats not right. You need to make a function virtual when you want to enable calling it based on the dynamic type of the object. And once you do have a virtual function you can use pointers or references to make use of the virtual dispatch.

You need to make a method virtual when you want to override it. As BaseClass::run is not overridden by Derived::run, there is no virtual dispatch and calling init from BaseClass::run calls BaseClass::init. If you want to enable virtual dispatch for BaseClass::init you need to declare it virtual.

Further, consider that your code is equivalent to:

void run(const int object) { 
    cout<<"BaseClass::run calls =>";    
    this->init(object); 
}

this is BaseClass*, hence BaseClass::init is called. You are calling init via a pointer, but that does not matter because init is not virtual. If you want to call init based on the dynamic type of the object, thats exactly what virtual is good for. The "overhead" you refer to in comments is not really overhead, but just the minimum needed to get the behavior you want. If you can change the design and do not need runtime polymorphism you can take a look at CRTP wich is a form of static polymorphism.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I can understand why we need to make a function virtual when calling using a base class pointer. But here we are using derived class Object which has information about "Derived::init" so this behavior is counter intuitive to me. – Shakti Malik Mar 07 '22 at 09:15
  • @ShaktiMalik there is no overriding in your code. I have seen bad tutorials where similar code is used to explain overriding, but it is not overriding. To override `init` it must be virtual. When you don't override it then calling `init` in `BaseClass` does call `BaseClass::init` – 463035818_is_not_an_ai Mar 07 '22 at 09:17
  • After this experiment I know that I need to make it virtual to make it work Which has associated overhead. I wanted to get an answer to "What is the rationale behind keeping such behavior ?" – Shakti Malik Mar 07 '22 at 09:19
  • @ShaktiMalik the overhead you refer to is virtual dispatch, which would be the behaviour your want, not more. Its not "overhead" when thats just the functionality you desire. Its like saying "I want virtual dispatch without the overhead of virtual dispatch". Sorry, I don't understand what explaination you are looking for. Thats just how calling a non-virtual function works, what would be the rationale to use virtual dispatch on a non-virtual method? – 463035818_is_not_an_ai Mar 07 '22 at 09:26