0

I was reading about Virtual Functions from the book "The C++ Programming Langauge" by Bjarne Stroustrup, and encountered the following code snippet:-

class A {
    //...
    protected:
    int someOtherField;
    //...
    public:
    virtual void print() const;
    //...
};

class B : public A {
    //...
    public:
    void print() const;
    //...
};

void B::print() const {
     A::print();
     cout<<A::someOtherField;
     //...
} 

It is written in the book that

"Calling a function using the scope resolution operator(::) as done in B::print() ensures that virtual mechanism is not used. Otherwise, B::print() would suffer infinite recursion."

I do not understand why this is the case, since, a call to the base class function correctly and explicitly tells that we are calling A::print() and not anything else. Why this may lead to infinite recursion?

Edit - I misplaced the keyword "virtual", I am extremely sorry for that, but still exploring this question also, What would happen if the following code was there?

  • @HTNW's comment provides proper insights
class A {
   //...
   void print() const;
   //...
}

class B : public A {
   //...
   virtual void print() const;
   //...
}
  • 1
    This will not lead to infinite recursion. In *"Otherwise, B::print() would suffer infinite recursion."* the "otherwise" means to **not** use `::`. – StoryTeller - Unslander Monica Aug 24 '20 at 17:50
  • 2
    If `::` didn't disable the virtual dispatch, then writing `A::print()` would call `B::print()`, because, well, the latter overrides the former. – HolyBlackCat Aug 24 '20 at 17:51
  • 2
    There are multiple subtle issues here, each of which illustrate why C++ can be so dangerous - why it's often so very, very easy to inadvertently write *DEFECTIVE* code in C++. In particular, you'll notice that B::print() doesn't [override](https://en.wikipedia.org/wiki/Method_overriding) A::print() (as one might expect of a virtual function); it [shadows](https://en.wikipedia.org/wiki/Variable_shadowing) it. Hence the need to qualify it, using the scope operator. – paulsm4 Aug 24 '20 at 18:00
  • 1
    @HolyBlackCat Nit: `B::print` doesn't *override* `A::print`, does it? `A::print` isn't `virtual`. `B::print` hides `A::print` by dominance. E.g. `A &&x = B(); x.print();` calls `A::print` by static dispatch. "Disabling virtual dispatch" is distinct from just "bypassing virtual dispatch", and only the latter is demonstrated here. IMO, "disabling virtual dispatch" would only be shown if `A::print` were also `virtual`. – HTNW Aug 24 '20 at 18:08

2 Answers2

1

Otherwise, B::print() would suffer infinite recursion.

This refers to the code without the A:::

void B::print() const {
     print();
     cout<<A::someOtherField;
     //...
}

This would just make B::print a recursive function, not what the author intended.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

If qualified call A::print() did not disable virtual dispatch, the usage like presented in B::print() would be infinite recursion and it would be pretty much impossible to call function from base class.

See the imaginary code execution if qualified call did not disable virtual dispatch:

  1. You have A* a = new B;
  2. a->print() is called, virtual dispatch determines that B::print() should be called
  3. The first instruction of B::print() calls A::print(), virtual dispatch determines that B::print() should be called
  4. Infinite recursion

Now, an execution sequence when qualified call disables virtual dispatch:

  1. You have A* a = new B;
  2. a->print() is called, virtual dispatch determines that B::print() should be called
  3. The first instruction of B::print() calls A::print(), exactly this function is called
  4. A::print() does its things and finishes
  5. B::print() continues its execution.
  6. No recursion happens.
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • The first call to print() will use the dynamic dispatch mechanism, but why it uses the mechanism again when it is clearly stated to call A::print()? Is the virtual mechanism used for all the calls initiated by the instance object? – Ankit Kumar Aug 25 '20 at 15:37
  • 1
    @AnkitKumar The first sequence is hypothetical - what would happen if `A::print()` did not prevent virtual dispatch. Sorry if I misunderstood your question. – Yksisarvinen Aug 26 '20 at 11:24
  • You answered correctly, thank you for that, but what I fail to understand is that will every call to print() (Hypothetically!) use the Dynamic Dispatch Mechanism, in this cases, even if it's inside B::print()? Can I conclude that every call will be replaced by some "vptr" by the compiler? Does this mean that a non-physical print() is created which just provides an abstract interface to all the other print()'s? – Ankit Kumar Aug 26 '20 at 13:19
  • 1
    @Ankit Yes, every unqualified call to `print()`, when `print()` is `virtual`, will use dynamic dispatch. Details how compiler deal with that are implementation specific, but most add vptr as hidden member to class object and one common vtable for every class with `virtual` functions somewhere in memory. Then call to `print()` will be replaced by compiler with `vptr[functionId]` and depending on what `vptr` is, function from correct vtable will be called. – Yksisarvinen Aug 26 '20 at 14:04
  • One last doubt, Why does the A::print() call inside the void B::print() const {} uses the virtual mechanism, since, it is qualified to explicitly call the parent method? Thanks – Ankit Kumar Aug 26 '20 at 15:15