Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The logic of invoking virtual functions is not clear (or it is method hiding?)

(tested on msvc2017)

struct AAA
{
    virtual float run(int arg)
    {
        return 5.5f;
    }
};

struct BBB : AAA
{
    virtual bool run(double arg)
    {
        return false;
    }
};

struct CCC : BBB
{
    virtual float run(int arg)
    {
        return 7.7f;
    }

    virtual bool run(double arg)
    {
        return true;
    }
};


CCC c;
BBB* pb = &c;
pb->run(5); // call CCC::run(double arg), WHY?? 
pb->run((int)5); // call CCC::run(double arg), WHY?? 

Why does pb->run(5) call only CCC::run(double arg), but not CCC::run(int arg)?

Does the virtual method of a child class with a different signature overlap the interface of the base class?

like image 886
dimasafo Avatar asked Aug 20 '19 16:08

dimasafo


Video Answer


2 Answers

All is simple.

The class BBB has in fact two virtual functions. One is declared in its base class AAA

struct AAA
{
    virtual float run(int arg)
    {
        return 5.5f;
    }
};

And other is declared in the class BBB itself.

struct BBB : AAA
{
    virtual bool run(double arg)
    {
        return false;
    }
};

The function declared in the class BBB hides the function declared in the class AAA. (Any name declared in a derived class hides an entity with the same name declared in the base class of the derived class)

In the class CCC the both functions are overriden.

These function calls

pb->run(5); // call CCC::run(double arg), WHY?? 
pb->run((int)5); // call CCC::run(double arg), WHY?? 

do not differ because their arguments have the type int.

The static type of the pointer pb is BBB *. So the compiler searches the name run in the class BBB.

Within the class only one function with this name is visible. It is the function declared in the class

virtual bool run(double arg)
{
    return false;
}

So the compiler runs this virtual function with this signature but invokes it using the table of virtual function pointers defined for the class CCC because the dynamic type of the pointer pb is CCC *.

You could make the function declared in the class AAA visible within the class BBB by means of the using declaration. For example

struct BBB : AAA
{
    using AAA:: run;
    virtual bool run(double arg)
    {
        return false;
    }
};

In this case the declaration of the function (declared in the class AAA) would be also a member declaration inside the class BBB. That is the class BBB will have declarations of two overloaded distinct virtual functions.

Here is a demonstrative program

#include <iostream>

struct AAA
{
    virtual float run(int arg)
    {
        return 5.5f;
    }
};

struct BBB : AAA
{
    using AAA:: run;
    virtual bool run(double arg)
    {
        return false;
    }
};

struct CCC : BBB
{
    virtual float run(int arg)
    {
        return 7.7f;
    }

    virtual bool run(double arg)
    {
        return true;
    }
};

int main() 
{
    CCC c;
    BBB* pb = &c;
    std::cout << pb->run(5) << '\n';
    std::cout << pb->run(5.6 ) << '\n';

    return 0;
}

Its output is

7.7
1

To make the situation with the member declarations in a derived class and in its base class more clear consider a similar situation with block scopes.

Here is a demonstrative program

#include <iostream>

void f( int ) { std::cout << "void f( int )\n"; }
void f( double ) { std::cout << "void f( double )\n"; }

int main() 
{
    void f( double );

    f( 5 );
    f( 5.5 );

    return 0;
}

The inner declaration of the function f in the block scope of the function main hides the other declaration of the function in the global scope.

The program output is

void f( double )
void f( double )
like image 158
Vlad from Moscow Avatar answered Oct 21 '22 01:10

Vlad from Moscow


When you do

struct BBB : AAA
{
    virtual bool run(double arg)
    {
        return false;
    }
};

run has a different signature from run in AAA. This means that BBB::run(double) will hide AAA::run(int). Since it does, the only run that you can call from BBB is bool run(double arg). When you do

pb->run(5);

it finds bool BBB::run(double arg) as that is the only function you can call statically from a BBB and then virtual dispatch kicks in calling CCC::run(double)


In order to get the int version of the function to be called, you need to bring the int version into BBB. You can do this by writing one, or you could use using AAA::run; to import it in. Doing either of those will make pb->run(5); call the int version of run from CCC.


Don't forget, when playing with polymorphism you should declare the top level destructor (AAA's in this case) to be virtual. This allows you to delete objects correctly when using dynamic allocation. For full details see: When to use virtual destructors?

like image 38
NathanOliver Avatar answered Oct 21 '22 03:10

NathanOliver