Why does non-pointer variable use base class method but pointer variable use derived class' method?

I'm trying to understand how polymorphism works. In particular, what "static linking" and "dynamic binding" means (terms used in [1]). Is there a good article or book chapter on C++ polymorphism that you know of?

How come f2 doesn't behave like f1?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

class Foo
{
public:
    virtual void Speak() { std::cout << "Foo!\n"; }
};

class Foob : public Foo
{
public:
    void Speak() { std::cout << "Foob!\n"; }
};

class Fooz : public Foo
{
public:
    void Speak() { std::cout << "Fooz!\n"; }
};

int main()
{
    Fooz fz = Fooz();
    Foob fb = Foob();
    Foo *f1 = nullptr;
    Foo f2;

    std::cout << "Enter 'b' or 'z'.\n";
    char choice;
    std::cin >> choice; // 'z' or 'b'

    switch (choice)
    {
        case 'b':
            std::cout << "Chose case b.\n";
            f1 = &fb;
            f2 = fb;
            break;
        case 'z':
            std::cout << "Chose case z.\n";
            f1 = &fz;
            f2 = fz;
            break;
    }
    // Ok, intuitive. You picked the object that's assigned to f1 and f1 calls that object's respective method. 
    f1->Speak(); 
    f2.Speak();  // ?

    int i;
    std::cin >> i;
}


[1] Runtime Polymorphism in C++
https://www.softwaretestinghelp.com/runtime-polymorphism-in-cpp/
Last edited on
Foo f2;
f2 is a Foo object. Exactly a Foo object, not anything derived for a Foo, just a Foo object. The compiler can see this, right there. There, a Foo object is being created. No ambiguity, no unknowns, so when you call
f2.Speak();
the function being called is Foo::speak
The compiler can see all this at compile time. There is no possible alternative.


Foo *f1 = nullptr;
What kind of object does this point to. Right now, no object. Okay, but when it does, what kind of object will it be pointing to? Something that is a Foo, which could be a Foo, or a Foob , or a Fooz. The compiler does not know. This will have to be dealt with at runtime.

Every Foo object, every Fooz, every Foob, when it is created, is created with a hidden set of variables that indicate its functions. The vtable. Any object with virtual functions gets this.

https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/

When you call f1->Speak(), that vtable gets used. f1 is pointing at either a Foo or Fooz or Foob object, and that Foo or Fooz or Foob object has a vtable, and that vtable indicates to use either Foo::speak or Fooz::speak or Foob::speak.

So that's why they're different.

f2.Speak(); - compiler knows for sure that the function to call is Foo::speak

f1->Speak() - compiler doesn't know what function will be called, but at runtime the object being pointed to by f1 DOES know what its own speak function is, and that gets decided and used at runtime.
Last edited on
Ah. Mystery solved. Thanks!

Foo f2;
f2 is a Foo object. Exactly a Foo object, not anything derived for a Foo, just a Foo object. The compiler can see this, right there. There, a Foo object is being created. No ambiguity, no unknowns, so when you call
f2.Speak();
the function being called is Foo::speak
The compiler can see all this at compile time. There is no possible alternative.


Things work a little differently in C#, where an automatic variable behaves like Foo*:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;

namespace test
{
    public class Foo
    {
        public virtual void Speak()
        {
            Console.WriteLine("Foo!");
        }
    }

    public class Fooz : Foo
    {
        public override void Speak()
        {
            Console.WriteLine("Fooz!");
        }
    }

    public class Foob : Foo
    {
        public override void Speak()
        {
            Console.WriteLine("Foob!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Foo f1 = new Foo();
            f1.Speak(); // Foo!

            f1 = new Fooz();
            f1.Speak(); // Fooz!

            f1 = new Foob();
            f1.Speak(); // Foob!
        }
    }
}


*sigh*
Topic archived. No new replies allowed.