"Direct" vs "virtual" call to a virtual function
Asked Answered
G

2

11

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 virtual function?

This pertains to terminology, not technicality. I am asking for when a call is defined as being made "directly" vs "virtually". It does not pertain to vtables, or anything else that has to do with the implementation of these concepts.

Gumma answered 12/4, 2016 at 21:46 Comment(5)
Could you point where you read about direct. It's the first time I heard about that term.Helwig
msdn.microsoft.com/en-us/library/bw1hbe6y.aspx About the rules of compiler inlining.Gumma
Possible duplicate of What is Difference between Virtual Method call and Direct Method call in context of VTable?Helwig
@Helwig I don't agree that this is a duplication, but I admit the possibility that I just don't understand that it is. But in such case, I still find my question valid.Gumma
@xvan. They are very much relayed, but the answers don't talk about when a call to a virtual function is a direct call and when it is a virtual one, as this was not implicitly asked in the question.Scathing
A
18

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.

Aciniform answered 12/4, 2016 at 22:5 Comment(4)
So what you're saying is that, with a pointer to an object, I could specify which derived (or base) class holds the version of the vitrual function I want to call? For example, that if I have a pointer to a base class, and I specify that I want to call the [virtual] function that is in the base class, not an override from some derived class, then it becomes a direct call, because it can be solved at compile time?Gumma
@antiHUMAN: Yes, if you want to suppress virtual dispatch and call base class version, you can do it by using a qualified name. In fact, this is exactly the syntax you use when/if you call base-class version of virtual function from derived-class version, e.g. void Derived::foo() { Base::foo(); }. except that in this case the object pointer is implicit (explicit version would be this->Base::foo()).Aciniform
Yes, I remember reading that in the "standard c++ bible", many years ago, and I do understand that concept. Well, this surely explains why direct vs virtual calls can have an effect on compiler iniling. You gave a really good answer.Gumma
@antiHUMAN: Inlining, of course, should be treated in the second context. Every time the compiler is smart enough to perform a direct call, the opportunity to inline immediately becomes available.Aciniform
R
6

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).

Rb answered 12/4, 2016 at 21:50 Comment(14)
Please include an explanation aside from just code which elaborates on the differences in technical language.Brainwashing
I'm not sure that's what is normally meant by a "direct" function call. The call to a.myfunc() can still be a call to a virtual function, and in fact, if the class X is changed so that it inherits from another class that declares myfunc as virtual, then exactly which myfunc is being called, as in whether it's a member of X or its base class, definitely depends on whether it's overridden in class X.Inapproachable
Since "X" has no derived classes, why is it considered a polymorphic object? Also will final make it a direct call, again?Gumma
@DanKorn In X a; a.myfunc(); there is no need for the compiler to use the vtable, no matter if myfunc is later overridden. The compiler has all the information it needs before the program runs, so it can generate safely the calling code. That's why probably some call it a "direct" call, although the term is non-standard. And I have no idea why this answer is being downvoted.Twomey
OK, so if I make a plain object of a derived class, and call a virtual member function of the base class or the derived class, does any of this affect whether it is a "direct" call to those virtual functions? Can a plain object ever made a virtual call?Gumma
@MichaelJ.Gray yes, sorry, I was a little bit fast in my answer. In the mean time, I've completed and hope it's clrearer :-)Rb
@Gumma in fact, the optimizer may find out that the type is determined and make a direct call. But not all compiler does it and it's not always possible, event with final. For this you can see more extensive answer on this specific aspect in this SO answer.Rb
@Gumma "does a plain object ever does a virtual call". In fact the object makes the call. The compiler will find out the most efficient way to perform it. But in general, for a plain object (not a reference), I assume that it should always be direct.Rb
@Cristophe If I know exactly what the terms means, then I could know how specific compilers treat these things. In fact, this is exactly why I'm asking. I am trying to understand the rules for microsoft's compiler inlining, and to understand htat I need to understand the terms they use.Gumma
@Gumma Microsoft's article means that inline is possible only if the virtual functions to be called can be defined at compile time. So yes, if no other condition opposes, for a plain object you may certainly end-up with inlining.Rb
@Cristophe Yes, now that I understand the terms, it's very clear and obvious why this is important when resolving inlining. Your answer was very useful, aswell.Gumma
@Rb Excellent, I've turned my downvote into an upvote for you :)Brainwashing
@Rb Just for my mind bro, if I have derived class Y, and object of it y. and I override myfunc() in class Y. Does y.myfunc() is considered as virtual call?Diskin
@AccessDenied the compiler will in practice call myfunc() directly, because the type is known at compile time. If you use a pointer to Y, it's a virtual call. Here the demo: gcc.godbolt.org/z/qEff1zx4j - In fact, in your specific case, virtual or direct call would both lead to the same overridden function; the optimizer will make sure that the most efficient alternative is chosen.Rb

© 2022 - 2024 — McMap. All rights reserved.