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

5

35

Given the following code fragment, what are the differences in the function calls? What is function hiding? What is function overriding? How do they relate to function overloads? What is the difference between the two? I couldn't find a good description of these in one place, so I'm asking here so I can consolidate the information.

class Parent {
  public:
    void doA() { cout << "doA in Parent" << endl; }
    virtual void doB() { cout << "doB in Parent" << endl; }
};

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

Parent* p1 = new Parent();
Parent* p2 = new Child();
Child* cp = new Child();

void testStuff() {
  p1->doA();
  p2->doA();
  cp->doA();

  p1->doB();
  p2->doB();
  cp->doB();
}
Leffert answered 1/11, 2013 at 22:15 Comment(4)
@kerrek-sb I can't find an explanation online (which is where I usually look, and others probably do too) that compares these. They have explanations of one or the other, but not both, and I wanted to put something online that would be easy to reference. Thanks for the catch on the access specifiers.Leffert
@karadoc No, it's just something that I found. The explanations I've found explain one or the other, but don't give a comparison of them, and that's what I'm trying for.Leffert
@karadoc, FWIW, I was about to post this same question and no, it is not for a homework assignment. I am long out of college. Thanks for positing this Jed, although unfortunately I still don't understand what the virtual keyword is for, unless you're trying to make an abstract base class with pure virtual functions. Apart from that, I fail to see what "virtual" does from a practical standpoint, since I can just hide the function.Farseeing
Nothing wrong if this sounds like a homework assignment. I not doing any homework, this exact question came into my head, I googled it, and found this great resource. I imagine many others are in the same situation. It is incredibly usefully to find the answer immediately.Correct
M
44

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)`
};
Murvyn answered 1/11, 2013 at 23:49 Comment(5)
Thanks! You explained this more cleanly and clearly than I did in my own answer.Leffert
@JedSchaaf I did not include the return type in the comments on purpose: Only the name is relevant for name hiding, and only the name plus the parameters are relevant for overriding. The return type can only make an override illegal, if it's not identical or covariant to the return type of the overridden function.Murvyn
On that same line, though, you have that vf2(int) overrides and hides vf(int). An edit has to have at least 6 characters, so I added something that I thought might be irrelevant. Sorry for not being clear. :/Leffert
@JedSchaaf Oh, thanks! That's actually a typo. Yeah.. the editing system of SO discourages (or shall discourage) these kind of small edits, as every edit has to be reviewed by several people. See this discussion on Meta Stack Overflow.Murvyn
It's also worth noting that if a non-virtual method Base::foo() calls a virtual method Base::vf() in the base class, then calling foo() from a derived class will trigger the overriding method Derived::vf(), and not the original Base::vf().Hippel
T
7

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.

Thisbe answered 27/6, 2015 at 22:21 Comment(2)
Thanks, the static dynamic piece was what I was looking for; makes sense. So function hiding is better in terms of speed, whenever it can be used, but if the base class type is being used that typically won't be the case.Jarv
The first paragraph sums up any possible answer and any 100 examples. Thanks!Birdbath
L
3

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().

Leffert answered 1/11, 2013 at 22:15 Comment(2)
@ChristopherStevenson Probably because it's the same guy who asked the question. So it looks like he just asked the question to get some reputation points rather than to actually learn something.Palgrave
Why do you assume I didn't learn this? I hadn't looked at these topics for a while, so I decided to look it up. When I couldn't quickly find exactly what I was looking for, I decided to add it here as a reference for others who might have similar questions. Do you not like that I answered my own question? The question submission has the option of adding an answer immediately, to "share your knowledge, Q&A style."Leffert
L
2

A much easier example that differs b/w all of them.

class Base {
public:
    virtual int fcn();
};

class D1 : public Base {
public:  
    // D1 inherits the definition of Base::fcn()
    int fcn(int);  // parameter list differs from fcn in Base
    virtual void f2(); // new virtual function that does not exist in Base
};

class D2 : public D1 {
public:
    int fcn(int); // nonvirtual function hides D1::fcn(int)
    int fcn();  // overrides virtual fcn from Base
    void f2();  // overrides virtual f2 from D1
}
Leede answered 31/3, 2015 at 18:35 Comment(0)
P
1

The example code you're writen in the question essentially gives the answer when you run it.

Calling a non-virtual function will use the function from the same class as the pointer type, regardless of whether the object was actually created as some other derived type. Whereas calling a virtual function will use the function from the original allocated object type, regardless of what kind of pointer you're using.

So your program's output in this case will be:

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child
Palgrave answered 1/11, 2013 at 23:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.