Search code examples
c++classpolymorphismvirtualundefined-behavior

Which virtual method is executed in an inheritance hierarchy when intermediate classes skip the implementation?


I'm sure that someone has asked this question before but I simply don't know what to search for. So I'm happy to remove this question as soon as someone points me to a similar one. I'm also happy to rename the questions if someone has a better suggestion :-)

I want to know if the following code is defined behavior by the standard or if this might be compiler/platform dependent:

struct A
{
    virtual void f()
    {
        std::cout << "A::f()" << std::endl;
    }
};

struct B : public A
{
    // f() is not implemented here
};

struct C : public B
{
    virtual void f() override
    {
        B::f(); // call f() of direct base class although it is not implemented there
        std::cout << "C::f()" << std::endl;
    }
};

int main()
{
    A* pA = new C();
    pA->f();
}

The output with Visual Studio 2017 and gcc 5.4.0 is:

A::f()
C::f()

Is it true that the compiler will search upwards in the hierarchy until it finds an implementation? Can you link to the C++ standard? I've tested it by making f() in A pure virtual and the linker nicely tells me that there is an unresolved symbol. Can I rely on that?

As I understand it using the scope operator like B::f() always calls the non-virtual version. So there is no polymorphism happening ever, is it?

Edit: The print statements where misleading, replaced "B::f()" with "C::f()".


Solution

  • The dynamic type of the pointer

    A* pA = new C();
    

    is C *.

    So the virtual function in the class C is called

    struct C : public B
    {
        virtual void f() override
        {
            B::f(); // call f() of direct base class although it is not implemented there
            std::cout << "B::f()" << std::endl;
        }
    };
    

    The class B does not redefine the virtual function of the base class A. So in this statement

            B::f(); // call f() of direct base class although it is not implemented there
    

    the virtual function defined in the class A is called. That is the table of pointers to virtual functions of the class B contains the address of the function defined in the class A.

    In this call

    B::f();
    

    there is access to the table of virtual functions of the class B and this table contains the address of the function definition in the class A because the function was not overriden in the class B.

    From the C++ STandard (5.2.2 Function call)

    1. ...If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider (10.3) in the dynamic type of the object expression is called; such a call is referred to as a virtual function call. [