Virtual functions with different argument types
Asked Answered
N

3

8

I'm trying to understand how virtual functions work, and I am stuck at a certain part.

I have written this small programm:

class First
{
public:
    virtual void f(int a) 
    {
        cout << "First!" << endl;
        cout << a << endl;
    }
};

class Second : public First
{
public:
    void f(int a) {
        cout << "Second!" << endl;
        cout << a << endl;
    }
};

void main() {
    Second s;
    First *p = &s;
    p->f(5);
    First n;
    p = &n;
    p->f(3);
    _getch();
}

This code results in:

Second!
5
First!
3

However, if I change int in the Second::f() function to a different type, like this:

class First
{
public:
    virtual void f(int a) {
        cout << "First!" << endl;
        cout << a << endl;
    }
};

class Second : public First
{
public:
    void f(double a) { //double instead int here!
        cout << "Second!" << endl;
        cout << a << endl;
    }
};

void main() {
    Second s;
    First *p = &s;
    p->f(5);
    First n;
    p = &n;
    p->f(3);
    _getch();
}

My program never calls Second::f(), and I'm getting this as a result:

First!
5
First!
3

Can someone explain to me why this happens?

Nagual answered 26/4, 2017 at 20:44 Comment(13)
Because that breaks their "connection"? void f(int a) and void f(double a) are two distinct function declarationsSwaney
You don't override base function since they differ in declaration.Spermatophyte
Try p->f(5.0); to see the difference.Postmistress
void f(int a) should be virtual void f(int a) override in the Second class if you want to override it...Lytle
@Lytle I do not think so. Once a virtual stays virtual. Repeating the keyword in inheriting classes is common practice and makes sense, but is not needed.Postmistress
@Lytle override is not required (and didn't exist before C++11)Swaney
@Postmistress agreed!Lytle
@Yunnosch: No, the output will be the same, because the static type is First.Offen
@ChristianHackl ... and First has no float function to be overridden. Damn, you are right. So: Declare a version with float parameter in First, then try p->f(5.0);. That demonstrates that the int version is not affected by a float version in Second. Only the float version in First is overridden.Postmistress
I meant "double" in all cases I wrote "float".Postmistress
@Lytle @UnholySheep: while override is not required it was introduced to help in exactly such cases. If override was used compiler would error that void f(double) doesn't override anything pointing at the problem. I'm surprised both answers (so far) doesn't mention itBal
Moral of the story: Overloading and overriding are two different things which should never be mixed.Offen
And by the way: void main is not legal C++ (it must be int main), using namespace std is a bad habit and _getch(); solves the wrong problem.Offen
R
13

When using virtual function dispatch, the so-called "final overrider" is what gets called. For a function to even override an inherited virtual function, it must meet some criteria:

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 (8.3.5), cv-qualification, and refqualifier (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.

-- ISO/IEC 14882:2001(E) §10.3 (bold emphasis mine)

Quite simply, in your second example the parameter list for Second::f(double) differs from that of First::f(int), so Second::f(double) is not (automatically) virtual and does not override First::f(int).

The C++11 keyword override declares your intent that a method override an inherited virtual method so that the compiler can tell you when it does not. For example, had you done this instead:

void f(double a) override {

The compiler would have given you this diagnostic to inform you that it doesn't actually override anything, and it even informs you why it doesn't ("type mismatch at 1st parameter ('int' vs 'double')"):

main.cpp:15:18: error: non-virtual member function marked 'override' hides virtual member function
void f(double a) override { //double instead int here!
                 ^
main.cpp:7:14: note: hidden overloaded virtual function 'First::f' declared here: type mismatch at 1st parameter ('int' vs 'double')
virtual void f(int a) {
             ^
Requiem answered 26/4, 2017 at 21:9 Comment(0)
S
3

in fact derived classes do not redeclare virtual functions of base classes. They redefine them that in the terms of C++ means to override definitions of virtual functions of base classes.

In your second example the derived class declares a new non-virtual function (because the function specifier virtual is absent) with the same name as the name of the virtual function in the base class. The newly declared function hides the declaration of the virtual function in the base class.

In this code snippet

Second s;
First *p = &s;
p->f(5);

the static type of the pointer p is First. So the compiler looks through the table of virtual functions declared in the class First and finds the pointer to the function declared in the class First. This pointer is not overwritten by the address of a virtual function in the derived class because the derived class did not override the base class function.

If you will write for example

Second s;
Second *p = &s;
^^^^^^
p->f(5);

then the non-virtual function declared in the derived class will be called.

Sewn answered 26/4, 2017 at 20:59 Comment(0)
D
1

In the second piece of code, Second inherits a virtual function called f() and has its own function called also f().

Now, since all calls are done from First (or an object pointed to by First) it works as follows: When you call from a First object, it's obvious that the function in First will be called. Now, when you call from a Second object pointed to by a pointer to First then since f() is virtual, the "compiler" [please see a comment below] searches for it in the actual class (which is Second) but since there is no function there that matches void f(int) then it takes the one from First.

Dardanelles answered 26/4, 2017 at 21:1 Comment(7)
JVM searcher?! This isn't Java.Offen
Interpreter? C++ is a compiled languageSwaney
@ChristianHackl That was a typo. Thanks.Dardanelles
"the compiler" is not really correct, either, because virtual functions are usually resolved at runtime (not necessary in this mini example, but in real life, the concrete subclass may not be known until runtime). The compiler inserts a mechanism by which this resolution can take place.Offen
@Swaney I wasn't sure which. I was about to check. Thanks.Dardanelles
@ChristianHackl That is why I thought it is the interpreter first!Dardanelles
@RoaaGharra: Nothing stops anyone from creating a C++ interpreter. The language specification itself is neutral about that. However, nobody has ever seen the practical use case for a C++ interpreter, I guess.Offen

© 2022 - 2024 — McMap. All rights reserved.