What are the differences between overriding virtual functions and hiding non-virtual functions?

What is function hiding?

... is a form of name hiding. A simple example:

void foo(int);
namespace X
{
    void foo();
    
    void bar()
    {
        foo(42); // will not find `::foo`
        // because `X::foo` hides it
    }
}

This also applies to the name lookup in a base class:

class Base
{
public:
    void foo(int);
};

class Derived : public Base
{
public:
    void foo();
    void bar()
    {
        foo(42); // will not find `Base::foo`
        // because `Derived::foo` hides it
    }
};

What is function overriding?

This is linked to the concept of virtual functions. [class.virtual]/2

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.

class Base
{
private:
    virtual void vf(int) const &&;
    virtual void vf2(int);
    virtual Base* vf3(int);
};

class Derived : public Base
{
public: // accessibility doesn't matter!
    void vf(int) const &&; // overrides `Base::vf(int) const &&`
    void vf2(/*int*/);     // does NOT override `Base::vf2`
    Derived* vf3(int);     // DOES override `Base::vf3` (covariant return type)
};

The final overrider becomes relevant when calling a virtual function: [class.virtual]/2

A virtual member function C::vf of a class object S is a final overrider unless the most derived class of which S is a base class subobject (if any) declares or inherits another member function that overrides vf.

I.e. if you have an object of type S, the final overrider is the first overrider you see when traversing the class hierarchy of S back to its base classes. The important point is that the dynamic type of the function-call expression is used in order to determine the final overrider:

Base* p = new Derived;
p -> vf(42);    // dynamic type of `*p` is `Derived`

Base& b = *p;
b  . vf(42);    // dynamic type of `b` is `Derived`

What is the difference between overriding and hiding?

Essentially, the functions in the base class are always hidden by functions of the same name in a derived class; no matter if the function in the derived class overrides a base class' virtual function or not:

class Base
{
private:
    virtual void vf(int);
    virtual void vf2(int);
};

class Derived : public Base
{
public:
    void vf();     // doesn't override, but hides `Base::vf(int)`
    void vf2(int); // overrides and hides `Base::vf2(int)`
};

To find a function name, the static type of an expression is used:

Derived d;
d.vf(42);   // `vf` is found as `Derived::vf()`, this call is ill-formed
            // (too many arguments)

How do they relate to function overloads?

As "function hiding" is a form of name hiding, all overloads are affected if the name of a function is hidden:

class Base
{
private:
    virtual void vf(int);
    virtual void vf(double);
};

class Derived : public Base
{
public:
    void vf();     // hides `Base::vf(int)` and `Base::vf(double)`
};

For function overriding, only the function in the base class with the same arguments will be overriden; you can of course overload a virtual function:

class Base
{
private:
    virtual void vf(int);
    virtual void vf(double);
    void vf(char);  // will be hidden by overrides in a derived class
};

class Derived : public Base
{
public:
    void vf(int);    // overrides `Base::vf(int)`
    void vf(double); // overrides `Base::vf(double)`
};

The difference between calling a virtual member function and calling a non-virtual member function is that, by definition, in the former case the target function is chosen in accordance with the dynamic type of the object expression used in the call, while in the latter case the static type is used.

That's all there is to it. Your example clearly illustrates this difference by p2->doA() and p2->doB() calls. Static type of *p2 expression is Parent, while dynamic type of the same expression is Child. This is why p2->doA() calls Parent::doA and p2->doB() calls Child::doB.

In contexts in which that difference matters, name hiding does not come into the picture at all.


We'll start with the easy ones.

p1 is a Parent pointer, so it will always call Parent's member functions.

cp is a pointer to Child, so it will always call Child's member functions.

Now the more difficult one. p2 is a Parent pointer, but it is pointing to an object of type Child, so it will call Child's functions whenever the matching Parent function is virtual or the function only exists within Child and not in Parent. In other words, Child hides Parent::doA() with its own doA(), but it overrides Parent::doB(). Function hiding is sometimes considered a form of function overloading, because a function with the same name is given a different implementation. Because the hiding function is in a different class than the hidden function, it does have a different signature, which makes it clear which to use.

The output for testStuff() will be

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child

In any case, Parent::doA() and Parent::doB() can be called within Child using name resolution, regardless of the function's "virtual-ness". The function

void Child::doX() {
  doA();
  doB();
  Parent::doA();
  Parent::doB();
  cout << "doX in Child" << endl;
}

demonstrates this when called by cp->doX() by outputting

doA in Child
doB in Child
doA in Parent
doB in Parent
doX in Child

Additionally, cp->Parent::doA() will call Parent's version of doA().

p2 cannot refer to doX() because it is a Parent*, and Parent doesn't know about anything in Child. However, p2 can be casted to a Child*, since it was initialized as one, and then it can be used to call doX().