1

I was trying to find more info on the matter but could only find this: In C++ why and how are virtual functions slower?

The answer says that the virtual call "Get[s] the right function address from the vtable into a register (the index where the correct function address is stored is decided at compile-time).".

But how does it know which is the correct function (address) in the first place? Doesn't it have to check which type the underlying object has in order to do so, thus being similar to a branching switch statement?

ZeroZ30o
  • 121
  • 5
  • The *implementation* of virtual dispatch, at the object code level, is generally similar to equivalent switch-based branching. That's what that answer is saying. *Both* are slower than *doing nothing* – Caleth Oct 23 '18 at 13:58
  • The crucial point is that every object that supports virtual dispatch holds a pointer to the vtable of its class. So a virtual method call `foo->bar(args)` would be compiled like a function pointer call `foo->_Vtable[_Index_for_bar_method](foo, args)`, ignoring any casts. The vtable of each class can be statically initialized. So vtables make it possible to get away without explicit type checks that would require branching. – amon Oct 23 '18 at 14:21

1 Answers1

4

The C++ compiler creates the vtable for each class. If classes A and B both are sub-classes of class Base, and override methods in base, then the vtable for A and the vtable for B are constructed with pointers to the respective overridden methods.

For the code where the virtual method is called, the entry in the vtable for the object is loaded with the address of the correct method for the sub-class.

Assuming that class Base has four virtual methods, m1, m2, m3, and m4, the vtable will have 4 entries and m3 will be the third entry in the table. Sub-class A overrides methods m1 and m4, while sub-class B overrides them all. The vtable for A will have four entries, with pointers to A::m1, Base::m2, Base:m3, and A::m4, while B will also have four entries, with pointers to B::m1, B::m2, B::m3, and B::m4. At the call site where m3 is being invoked, the third vtable entry is loaded with the correct method pointer because the compiler constructed the tables this way.

(Of course, there is much more to vtable construction and layout than I have presented, so your mileage may vary.)

BobDalgleish
  • 4,644
  • 5
  • 18
  • 23
  • So if I understand correctly, at runtime it would only have to check which vtable to choose (A's or B's), yes? – ZeroZ30o Oct 23 '18 at 14:40
  • It won't *check* which vtable to use. It will simply use the vtable of the object that is the receiver of the dispatch. The vtable of an object is always in the same well-known place within an object, as a hidden field. For `obj->vmethod()` it will do obj->_vtable[_vmethodConstantIndex]()`. See @amon's comment. – Erik Eidt Oct 23 '18 at 14:56
  • @ErikEidt but how does it know which vtable to use -say, if you have a vector with the derived classes inside (A and B), and you wanna call m2() on all of them, how could the program know the object's type directly? Does it not have to go check where the pointer is to see which is the underlying type? – ZeroZ30o Oct 23 '18 at 15:00
  • The vtable is a hidden field in the object. It fetches the vtable from this hidden field in the object. Invocation of a virtual method requires a receiver, so it uses that receiver and fetches the vtable from its hidden field. As it happens, we cannot invoke a virtual method without a receiver object. – Erik Eidt Oct 23 '18 at 15:01
  • @ErikEidt ah, I see now -so anytime you create a class that inherits from a parent owning a virtual method, a vtable is created inside that class? – ZeroZ30o Oct 23 '18 at 15:05
  • Yes, this the mechanism. The vtable is shared by all members created of the same type. All hidden vtable pointer fields are located in the same position (usually at offset 0) within all objects that have them. Multiple inheritance complicates things quite a bit, so then there will be multiple hidden vtable fields in the object (one at 0 and another elsewhere), and the generated code will refer to the object with an adjusted pointer to maintain the above. – Erik Eidt Oct 23 '18 at 15:09
  • What we're talking about is sometimes referred to as the "object model", sometimes as the runtime system. – Erik Eidt Oct 23 '18 at 15:14