Incomputer programming, avirtual method table (VMT),virtual function table,virtual call table,dispatch table,vtable, orvftable is a mechanism used in aprogramming language to supportdynamic dispatch (orrun-time methodbinding).
Whenever aclass defines avirtual function (ormethod), mostcompilers add a hiddenmember variable to the class that points to an array ofpointers to (virtual) functions called the virtual method table. These pointers are used at runtime to invoke the appropriate function implementations, because at compile time it may not yet be known if the base function is to be called or a derived one implemented by a class thatinherits from the base class.
There are many different ways to implement such dynamic dispatch, but use of virtual method tables is especially common amongC++ and related languages (such asD andC#). Languages that separate the programmatic interface of objects from the implementation, likeVisual Basic andDelphi, also tend to use this approach, because it allows objects to use a different implementation simply by using a different set of method pointers. The method allows creation of external libraries, where other techniques perhaps may not.[1]
Suppose a program contains three classes in an inheritance hierarchy: asuperclass,Cat, and twosubclasses,HouseCat andLion. ClassCat defines avirtual function namedspeak(), so its subclasses may provide an appropriate implementation (e.g. eithermeow() orroar()). When the program calls thespeak function on aCat reference (which can refer to an instance ofCat, or an instance ofHouseCat orLion), the code must be able to determine which implementation of the function the call should bedispatched to. This depends on the actual class of the object, not the class of the reference to it (Cat). The class cannot generally be determinedstatically (that is, atcompile time), so neither can the compiler decide which function to call at that time. The call must be dispatched to the right functiondynamically (that is, atrun time) instead.
An object's virtual method table will contain theaddresses of the object's dynamically bound methods. Method calls are performed by fetching the method's address from the object's virtual method table. The virtual method table is the same for all objects belonging to the same class, and is therefore typically shared between them. Objects belonging to type-compatible classes (for example siblings in an inheritance hierarchy) will have virtual method tables with the same layout: the address of a given method will appear at the same offset for all type-compatible classes. Thus, fetching the method's address from a given offset into a virtual method table will get the method corresponding to the object's actual class.[2]
TheC++ standards do not mandate exactly how dynamic dispatch must be implemented, but compilers generally use minor variations on the same basic model.
Typically, the compiler creates a separate virtual method table for each class. When an object is created, a pointer to this table, called thevirtual table pointer,vpointer orVPTR, is added as a hidden member of this object. As such, the compiler must also generate "hidden" code in theconstructors of each class to initialize a new object's virtual table pointer to the address of its class's virtual method table.
Many compilers place the virtual table pointer as the last member of the object; other compilers place it as the first; portable source code works either way.[3]For example,g++ previously placed the pointer at the end of the object.[4]
Consider the following class declarations inC++:
importstd;classBase1{private:intb1=0;public:explicitBase1(intb1):b1{b1}{}virtual~Base1()=default;voidnonVirtual(){std::println("Base1::nonVirtual() called!");}virtualvoidfn1(){std::println("Base1::fn1() called!");}};classBase2{private:intb2=0;public:explicitBase2(intb2):b2{b2}{}virtual~Base2()=default;virtualvoidfn2(){std::println("Base2::fn2() called!");}};classDerived:publicBase1,publicBase2{private:intd=0;public:explicitBase1(intb1,intb2,intd):Base1(b1),Base2(b2),d{d}{}~Derived()=default;voidfn3(){std::println("Derived::fn3() called!");}voidfn2()override{std::println("Derived::fn2() called!");}};intmain(){Base2*base2=newBase2();Derived*derived=newDerived();// ...deletebase2;deletederived;}
g++ 3.4.6 fromGCC produces the following 32-bit memory layout for the objectbase2:[nb 1]
b2: +0: pointer to virtual method table of Base2 +4: value of b2virtual method table of Base2: +0: Base2::fn2()
and the following memory layout for the objectd:
d: +0: pointer to virtual method table of Derived (for Base1) +4: value of b1 +8: pointer to virtual method table of Derived (for Base2) +12: value of b2 +16: value of dTotal size: 20 Bytes.virtual method table of Derived (for Base1): +0: Base1::fn1() // Base1::fn1() is not overriddenvirtual method table of D (for Base2): +0: Derived::fn2() // Base2::fn2() is overridden by Derived::fn2() // The location of Base2::fn2 is not in the virtual method table for Derived
Note that those functions not carrying the keywordvirtual in their declaration (such asnonVirtual() andd()) do not generally appear in the virtual method table. There are exceptions for special cases as posed by thedefault constructor.
Also note thevirtual destructors in the base classes,Base1 andBase2. They are necessary to ensuredelete derived; can free up memory not just forDerived, but also forBase1 andBase2, ifderived is a pointer or reference to the typesBase1 orB2. They were excluded from the memory layouts to keep the example simple.[nb 2]
Overriding of the methodfn2() in classDerived is implemented by duplicating the virtual method table ofBase2 and replacing the pointer toBase2::fn2() with a pointer toDerived::fn2().
The g++ compiler implements themultiple inheritance of the classesBase1 andBase2 in classDerived using two virtual method tables, one for each base class. (There are other ways to implement multiple inheritance, but this is the most common.) This leads to the necessity for "pointer fixups", also calledthunks, whencasting.
Consider the following C++ code:
Derived*derived=newDerived();Base1*base1=derived;Base2*base2=derived;
Whilederived andbase1 will point to the same memory location after execution of this code,base2 will point to the locationderived + 8 (eight bytes beyond the memory location ofderived). Thus,base2 points to the region withinderived that "looks like" an instance ofBase2, i.e., has the same memory layout as an instance ofBase2.[clarification needed]
A call toderived->fn1() is handled by dereferencingderived'sDerived::Base1 vpointer, looking up thefn1 entry in the virtual method table, and then dereferencing that pointer to call the code.
In the case of single inheritance (or in a language with only single inheritance), if the vpointer is always the first element inderived (as it is with many compilers), this reduces to the following pseudo-C++:
(*((*derived)[0]))(derived)
Where*derived refers to the virtual method table ofDerived and[0] refers to the first method in the virtual method table. The parameterderived becomes the"this" pointer to the object.
In the more general case, callingBase1::fn1() orDerived::fn2() is more complicated:
// Call derived->fn1()(*(*(derived[0]/*pointer to virtual method table of Derived (for Base1)*/)[0]))(derived)// Call derived->fn2()(*(*(derived[8]/*pointer to virtual method table of Derived (for Base2)*/)[0]))(derived+8)
The call toderived->fn1() passes aBase1 pointer as a parameter. The call toderived->fn2() passes aBase2 pointer as a parameter. This second call requires a fixup to produce the correct pointer. The location ofBase2::fn2 is not in the virtual method table forDerived.
By comparison, a call toderived->fnonvirtual() is much simpler:
(*Base1::fnonvirtual)(derived)
A virtual call requires at least an extra indexed dereference and sometimes a "fixup" addition, compared to a non-virtual call, which is simply a jump to a compiled-in pointer. Therefore, calling virtual functions is inherently slower than calling non-virtual functions. An experiment done in 1996 indicates that approximately 6–13% of execution time is spent simply dispatching to the correct function, though the overhead can be as high as 50%.[5] The cost of virtual functions may not be so high on modernCPU architectures due to much larger caches and betterbranch prediction.
Furthermore, in environments whereJIT compilation is not in use, virtual function calls usually cannot beinlined. In certain cases it may be possible for the compiler to perform a process known asdevirtualization in which, for instance, the lookup and indirect call are replaced with a conditional execution of each inlined body, but such optimizations are not common.
To avoid this overhead, compilers usually avoid using virtual method tables whenever the call can be resolved atcompile time.
Thus, the call tofn1 above may not require a table lookup because the compiler may be able to tell thatderived can only hold aDerived at this point, andDerived does not overridefn1. Or the compiler (or optimizer) may be able to detect that there are no subclasses ofBase1 anywhere in the program that overridefn1. The call toBase1::fn1 orBase2::fn2 will probably not require a table lookup because the implementation is specified explicitly (although it does still require thethis-pointer fixup).
The virtual method table is generally a good performance trade-off to achieve dynamic dispatch, but there are alternatives, such asbinary tree dispatch, with higher performance in some typical cases, but different trade-offs.[1][6]
However, virtual method tables only allow forsingle dispatch on the special "this" parameter, in contrast tomultiple dispatch (as inCLOS,Dylan, orJulia), where the types of all parameters can be taken into account in dispatching.
Virtual method tables also only work if dispatching is constrained to a known set of methods, so they can be placed in a simple array built at compile time, in contrast toduck typing languages (such asSmalltalk,Python orJavaScript).
Languages that provide either or both of these features often dispatch by looking up a string in ahash table, or some other equivalent method. There are a variety of techniques to make this faster (e.g.,interning/tokenizing method names, caching lookups,just-in-time compilation).
-fdump-class-hierarchy (starting with version 8:-fdump-lang-class) argument can be used to dump virtual method tables for manual inspection. For AIX VisualAge XlC compiler, use-qdump_class_hierarchy to dump class hierarchy and virtual function table layout.{{cite web}}: CS1 maint: bot: original URL status unknown (link)