P
Pavel
No, it does not need to know "the complete definition". I am not sure aboutStuart said:I still don't agree. If you use single-inheritance under C++, there is no
performance penalty compared to any vtable-based programming language which
would result from the fact that C++ provides multiple-inheritance. Since the
vtables and the data members of classes can be laid out by the compiler in such
a fashion that any "this" pointer never has to be adjusted in a
single-inheritance hierarchy, C++ is as effecient as it ever gets.
[snip]Because the compiler does not know whether the base is the first
base in the particular most-derived class, ... [snip]
If the compiler wants to do anything with a class, it needs to know the complete
definition of the class and all its base classes.
"anything", but specifically for generating code that calls a virtual function
all the compiler needs to know is how to calculate the address of a particular
subobject of the object of the most-derived class pointed by the pointer given
to compiler. That particular subobject is uniquely defined by two requirements:
a) it contains the sub-subobject pointed to the given pointer as one of its bases
b) of all such sub-objects it is of the most derived class that overrode the
virtual function being called
such subobject can be calculated by
- subtracting an offset stored in the virtual table pointed by given pointer,
from that given pointer (that offset can be stored in the table in more than one
way)
- or by creating multiple virtual tables where some entries (those allocated by
the classes where the pointer's type is their first base) point to the actual
functions and the others point to pointer-adjusting thunks that forward control
to that original function after the adjustment.
None of the two ways is in general faster than the other (although I admit the
"thunk" way avoids direct penalties for calling virtual by pointers on first base).
So it knows whether a certain
No, in general it does not know it at the call site. In the example you snippedbase class is the first base class of another class or not.
form my previous post for no reason this fact was clearly demonstrated:
When compiler is to generate the code for the virtual call
bPtr->getBOffset() or bPtr2->getBOffset()
it does not have access to the definition of the most-derived class of the
object pointed to by bPtr.
The call site is in client.cpp. bPtr and bPtr2 point to an object of either
class D (that has B as the first base) or D2 (that has it as the second base)
but the compiler does not have definition of either of them in view because
these definitions are in d.h and d2.h, respectively, and client.cpp does not
include either of these two headers, directly or indirectly. Thus, compiler has
no way of knowing whether bPtr and bPtr2 point to the first base of their most
derived classes or not.
What you probably
It is a true statement by itself, but it is not what I am saying.tried to say is that the compiler, receiving a pointer to Base* cannot know
whether the real type of the object is Base or Derived,
so a "this"-pointer
Correct. Extra Invocation is not free though (even though it is not a fulladjustment may have to be performed when any of Derived's methods should be
invoked.
Note that this adjustment is most likely done inside a "thunk"-method which
simply adjusts the "this" pointer and invokes Derived's implementation method
that does not need to adjust the "this" pointer.
invocation but just a jump on most architectures; but jump is not free either).
Simple adjustment will work better for non-first bases; thus some
implementations still use it; and some classes will work faster with it.
They can by that creating the code bloating issue (for a change, unrelated toLike so:
class Base1 {
int b1;
virtual void print1 ();
};
class Base2 {
int b2;
virtual void print2 ();
};
class Derived : Base1, Base2 {
int derived;
virtual void print1 ();
virtual void print2 ();
};
,____________________ ___________ ,________________________
+0 | |vtable |->. print1 | |void Derived:rint2 { |
|Base1 |----------| | print2 |->| std::cout << derived;|
+4 | |int b1; | |_________| |//&derived == this+16; |
|________|__________| ___________ |} |
+8 | |vtable |->| print2 | |_______________________|
|Base2 |----------| |_________| ,________________________
+12 | |int b2; | | |__asm { |
|________|__________| |---->| this -= 8; |
+16 |int derived; | | call Derived:rint2 |
|___________________| |} |
|_______________________|
And even that is an implementation detail: A compiler writer may choose to
generate Derived:rint multiple times,
templates) with all its accompanying instructions-cache-use-efficiency issues.
Also, think of all the the nice linkage issues: what will be external names (or,
for that matter, addresses) for versions one and two of the Derived:rint2? The
definitive address matters because in C++ you need to provide the capability of
calling-by-pointer-to-member, for the 1st and non-1st bases.
BTW such calls via pointer-to-members, even via the first base-pointer cannot be
made as efficient as they could be in the languages with the single inheritance
(such as C++ version ARM1 ). It's all IMHO, of course, I have to be careful
now. This is because at the time pointer-to-member is initialized it is not yet
known whether it will be used with the pointer-to-the-first base or
pointer-to-the-higher-than-first base.
Long story short -- you cannot receive something (in our case classes that can
be interchangeably used as bases for both single and multiple inheritance) for
nothing (in our case no performance cost). (although I would agree it's worth to
die trying . )
each version using its own set of
offsets. This would eliminate the need for thunking code completely at the cost
of larger executables:
,____________________ ___________ ,________________________
+0 | |vtable |->. print1 | |void Derived:rint2 { |
|Base1 |----------| | print2 |->| std::cout << derived;|
+4 | |int b1; | |_________| |//&derived == this+16; |
|________|__________| ___________ |} |
+8 | |vtable |->| print2 | |_______________________|
|Base2 |----------| |_________|
+12 | |int b2; | | ,________________________
|________|__________| |---->|void Derived:rint2 { |
+16 |int derived; | | std::cout << derived;|
|___________________| |//&derived == this+8; |
|} |
|_______________________|
Regards,
Stuart
-Pavel