I am self-taught, and therefore am not familiar with a lot of terminology. I cannot seem to find the answer to this by googling: What is a \"virtual\" vs a \"direct\" call to a
The answer to your question is different at different conceptual levels.
At conceptual language level the informal term "virtual call" usually refers to calls resolved in accordance with the dynamic type of the object used in the call. According to C++ language standard, this applies to all calls to virtual functions, except for calls that use qualified name of the function. When qualified name of the method is used in the call, the call is referred to as "direct call"
SomeObject obj;
SomeObject *pobj = &obj;
SomeObject &robj = obj;
obj.some_virtual_function(); // Virtual call
pobj->some_virtual_function(); // Virtual call
robj.some_virtual_function(); // Virtual call
obj.SomeObject::some_virtual_function(); // Direct call
pobj->SomeObject::some_virtual_function(); // Direct call
robj.SomeObject::some_virtual_function(); // Direct call
Note that you can often hear people say that calls to virtual functions made through immediate objects are "not virtual". However, the language specification does not support this point of view. According to the language, all non-qualified calls to virtual functions are the same: they are resolved in accordance with the dynamic type of the object. In that [conceptual] sense they are all virtual.
At implementation level the term "virtual call" usually refers to calls dispatched through some implementation-defined mechanism, that implements the standard-required functionality of virtual functions. Typically it is implemented through Virtual Method Table (VMT) associated with the object used in the call. However, smart compilers will only use VMT to perform calls to virtual functions when they really have to, i.e. when the dynamic type of the object is not known at compile time. In all other cases the compiler will strive to call the method directly, even if the call is formally "virtual" at the conceptual level.
For example, most of the time, calls to virtual functions made with an immediate object (as opposed to a pointer or a reference to object) will be implemented as direct calls (without involving VMT dispatch). The same applies to immediate calls to virtual functions made from object's constructor and destructor
SomeObject obj;
SomeObject *pobj = &obj;
SomeObject &robj = obj;
obj.some_virtual_function(); // Direct call
pobj->some_virtual_function(); // Virtual call in general case
robj.some_virtual_function(); // Virtual call in general case
obj.SomeObject::some_virtual_function(); // Direct call
pobj->SomeObject::some_virtual_function(); // Direct call
robj.SomeObject::some_virtual_function(); // Direct call
Of course, in this latter sense, nothing prevents the compiler from implementing any calls to virtual functions as direct calls (without involving VMT dispatch), if the compiler has sufficient information to determine the dynamic type of the object at compile time. In the above simplistic example any modern compiler should be able to implement all calls as direct calls.
Suppose you have this class:
class X {
public:
virtual void myfunc();
};
If you call the virtual function for a plain object of type X, the compiler will generate a direct call, i.e. refer directly to X::myfunct()
:
X a; // object of known type
a.myfunc(); // will call X::myfunc() directly
If you'd call the virtual function via a pointer dereference, or a reference, it is not clear which type the object pointed to will really have. It could be X but it could also be a type derived from X. Then the compiler will make a virtual call, i.e. use a table of pointers to the function address:
X *pa; // pointer to a polymorphic object
... // initialise the pointer to point to an X or a derived class from X
pa->myfunc(); // will call the myfunc() that is related to the real type of object pointed to
Here you have an online simulation of the code. You'll see that in the first case, the generated assembly calls the address of the function, whereas in the second case, the compiler loads something in a register and make an indirect call using this register (i.e. the called address is not "hard-wired" and will be determined dynamically at run time).