Home > Mobile >  Base class function being called instead of overridden function defined in Derived class
Base class function being called instead of overridden function defined in Derived class

Time:03-07

Here are my code

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 "BaseClass::init" ? I though we need virtual functions only when calling through a pointer.

What is the rationale behind keeping such behavior ?

CodePudding user response:

Why "BaseClass::init" is being called instead of "BaseClass::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.

CodePudding user response:

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.

  • Related