Re: Multiple Inheritance vs. Interface
On 10/7/12 Pavel wrote:
C++ code does suffer performance
penalties from using multiple inheritance. Moreover, and what's
especially frustrating, even the code that does not use multiple
inheritance (in fact, any code using virtual functions) suffers from at
least one performance penalty imposed by the way C++ supports multiple
inheritance: the necessity to read the offset of the call target within
the object of the most-derived class overriding the virtual method and
subtracting this offset from the passed pointer to let the virtual
function implementation access to the object it expects.
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. So it knows
whether a certain base class is the first base class of another class or
not. What you probably 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 adjustment 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.
Like 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::print2 { |
|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::print2 |
|___________________| |} |
|_______________________|
And even that is an implementation detail: A compiler writer may
choose to generate Derived::print multiple times, 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::print2 { |
|Base1 |----------| | print2 |->| std::cout << derived;|
+4 | |int b1; | |_________| |//&derived == this+16; |
|________|__________| ___________ |} |
+8 | |vtable |->| print2 | |_______________________|
|Base2 |----------| |_________|
+12 | |int b2; | | ,________________________
|________|__________| |---->|void Derived::print2 { |
+16 |int derived; | | std::cout << derived;|
|___________________| |//&derived == this+8; |
|} |
|_______________________|
Regards,
Stuart