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
};
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.