'Should virtual dispatch happen when a virtual method is called within a virtual method using object?
struct B
{
virtual void bar () {}
virtual void foo () { bar(); }
};
struct D : B
{
virtual void bar () {}
virtual void foo () {}
};
Now we call foo()
using an object of B
as,
B obj;
obj.foo(); // calls B::bar()
Question:
Should bar()
will be resolved through virtual
dispatch or it will be resolved using the static type of the object (i.e. B
).
Solution 1:[1]
Entirely up to the implementation. Nominally it's a virtual call, but you're not entitled to assume that the emitted code will actually perform an indirection through a vtable or similar.
If foo()
is called on some arbitrary B*
, then of course the code emitted for foo()
needs to make a virtual call to bar()
, since the referand might belong to a derived class.
This isn't an arbitrary B*
, this is an object of dynamic type B
. The result of a virtual or non-virtual call is exactly the same, so the compiler can do what it likes ("as-if" rule), and a conforming program can't tell the difference.
Specifically in this case, if the call to foo
is inlined, then I'd have thought that the optimizer has every chance of de-virtualizing the call to bar
inside it, since it knows exactly what's in the vtable (or equivalent) of obj
. If the call isn't inlined, then it's going to use the "vanilla" code of foo()
, which of course will need to do some kind of indirection since it's the same code used when the call is made on an arbitrary B*
.
Solution 2:[2]
EDIT: I think I misunderstood your question. I'm pretty sure it depends on how smart the compiler's optimizer is. A naive implementation would of course still go through a virtual lookup. The only way to know for sure for a particular implementation is to compile the code and look at the disassembly to see if it's smart enough to make the direct call.
Original answer:
It will be virtually dispatched. This is more obvious when you consider that within a class method, a method call works out to something like this->bar();
, making it obvious that a pointer is used to call the method, allowing to use the dynamic object type.
However in your example since you created a B
it will of course call B
's version of the method.
Do note (as seen in a comment) that virtual dispatch doesn't happen inside constructors even using the implicit this->
.
EDIT2 for your update:
That's not right at all. Calls within B::foo()
cannot be generally bound statically (unless due to inlining the compiler knows the static type of the object). Just because it knows that it's being called on a B*
says nothing about the real type of the object in question - it could be a D*
and need virtual dispatch.
Solution 3:[3]
It must be a virtual call. The code that you're compiling cannot know if there's not a more-derived class that it actually is that has overridden the other function.
Note that this assumes that you're compiling these separately. If the compiler inlines the call to foo() (due to its static type being known) it'll also inline the call to bar().
Solution 4:[4]
Answer: from the language point of view call to bar()
inside B::foo()
is resolved through virtual dispatch.
The bottom line is that from the point of view of C++ language, virtual dispatch always happens when you call a virtual method using its non-qualified name. When you use a qualified name of the method, the virtual dispatch does not happen. That means that the only way to suppress virtual dispatch in C++ is to use this syntax
some_object_ptr->SomeClass::some_method();
some_object.SomeClass::some_method();
In this case the dynamic type if the object on the left-hand side is ignored and the specific method is called directly.
In all other cases virtual dispatch does happen, as far as the language is concerned. I.e. the call is resolved in accordance with the dynamic type of the object. In other words, from the formal point of view, every time you call a virtual method through an immediate object, as in
B obj;
obj.foo();
the method is called through the "virtual dispatch" mechanism, regardless of context ("within a virtual method" or not - doesn't matter).
That's how it is in C++ language. Everything else is just optimizations made by compilers. As you probably know, most (if not all) compilers will generate a non-virtual call to a virtual method, when the call is performed through an immediate object. This is, of course, an obvious optimization, since the compiler knows that the static type of the object is the same as its dynamic type. Again, it doesn't depend on the context ("within a virtual method" or not - doesn't matter).
Inside a virtual method, the call can be made without specifying an object on the left hand side (as in your example), which really implies all calls have this->
implicitly present on the left. The very same rules apply in this case as well. If you just call bar()
, it stands for this->bar()
and the call is dispatched virtually. If you call B::bar()
, it stands for this->B::bar()
and the call is dispatched non-virtually. Everything else will only depend on the optimization capabilities of the compiler.
What you are trying to say by "because, once you are inside B::foo()
, it's sure that this is of type B*
and not D*
" is totally unclear to me. This statement misses the point. Virtual dispatch depends on the dynamic type of the object. Note: it depends on the type of *this
, not on the type of this
. It doesn't matter at all what the type of this
is. What matters is the dynamic type of *this
. When you are inside B::foo
, it is still perfectly possible that the dynamic type of *this
is D
or something else. For which reason, the call to bar()
has to be resolved dynamically.
Solution 5:[5]
In this case:
B obj;
obj.foo(); // calls B::bar()
the compiler can optimize away the virtual dispatch since it knows that type of the actual object is B
.
However, inside of B::foo()
the call to bar()
needs to use virtual dispatch generally (though the compiler might be able to inline the call and for that particular call instance could possibly optimize the virtual dispatch away again). Specifically, this statement you proposed:
Irrespective of
foo()
is called using object or pointer/reference, all the calls inside any virtualB::foo()
should be statically resolved. Because, once you are inside B::foo(), it's sure that this is of typeB*
and notD*
is not true.
Consider:
struct D2 : B
{
// D2 does not override bar()
virtual void foo () {
cout << "hello from D2::bar()" << endl;
}
};
Now if you had the following somewhere:
D2 test;
B& bref = test;
bref.foo();
That call to foo()
would end up in B::foo()
, but when B::foo()
calls bar()
, it needs to dispatch to D2::bar()
.
Actually, now that I've typed this out, the B&
is completely unnecessary for this example.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Steve Jessop |
Solution 2 | |
Solution 3 | dascandy |
Solution 4 | |
Solution 5 | Michael Burr |