Multiple Inheritance vs. Interface

Discussion in 'C++' started by lieve again, Sep 20, 2012.

  1. lieve again

    lieve again Guest

    Hi!
    I have a question regarding the implementation of the multiple
    inheritance in C++.
    As far as I know, the implementation problem of multiple inheritance
    (one of them) in every programming language is the need for an extra
    pointer for each new inherited class.
    For example:
    class Base1{
    // hidden Base1_vtr = &Base1_vtbl[0]
    virtual void func();
    virtual void func2();
    virtual void func3();
    };
    class Base2{
    // hidden Base2_vtr = &Base2_vtbl[0]
    virtual base2func();
    virtual base2func2();
    };

    class NormalInheritance : public Base1 {
    // hidden Base1_vtr = &Base1_vtbl[0]
    virtual void anotherFunc();
    };

    class MultipleInheritance : public Base1, Base2 {
    // hidden Base1_vtr = &Base1_vtbl[0]
    // hidden Base2_vtr = &Base2_vtbl[0]
    virtual void anotherFunc();
    };

    So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance) == 4
    bytes (only one virtual pointer)
    but sizeof(MultipleInheritance) == 8 bytes
    if class MultipleInheritance would inherit from another Base3, the
    size would be 12 bytes and so on.

    So with multiple inheritance we ends with big classes because of the
    need of extra virtual pointers,
    to avoid that, almost every language doesn't implement multiple
    inheritance but Interfaces, where one
    can only inherit more than one class but being that classes abstract
    or pure virtual, like:

    class Base1{
    virtual void func() = 0;
    virtual void func3();
    };

    class MixedClass : public NormalClass implements Base1, Base2,
    Base...

    My question is: Don't we have the same implementation problem as in C+
    +? Because even being these classes
    abstract, they need a virtual pointer. Why do they impose that rule
    in languages like C#, Java, D...?
    Someone know the reason?
     
    lieve again, Sep 20, 2012
    #1
    1. Advertising

  2. lieve again

    Öö Tiib Guest

    On Thursday, September 20, 2012 3:59:04 PM UTC+3, lieve again wrote:
    > So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance) == 4
    > bytes (only one virtual pointer)
    > but sizeof(MultipleInheritance) == 8 bytes
    > if class MultipleInheritance would inherit from another Base3, the
    > size would be 12 bytes and so on.


    It depends on implementation. "virtual" is just a keyword in C++ language.
    While vtable is one usual way to implement it ... and multiple vtables is one
    way to implement multiple inheritance ... it is NOWHERE written that you have
    to do so. The programs written in C# are about 5 times less efficient than same
    in C++ and so you could draw false conclusions that multiple inheritance is 5
    times more efficient than no multiple inheritance.

    > So with multiple inheritance we ends with big classes because of the
    > need of extra virtual pointers,


    You are mixing classes with objects here. You should stop. Just take few steps
    back and learn basics. What is class what is object and what is the difference.
     
    Öö Tiib, Sep 22, 2012
    #2
    1. Advertising

  3. lieve again

    Aitor Guest

    > > So with multiple inheritance we ends with big classes because of the
    > > need of extra virtual pointers,

    >
    > You are mixing classes with objects here. You should stop. Just take few steps
    > back and learn basics. What is class what is object and what is the difference.


    First thank you for the answers.

    I didn't mean classes but object or the instances of the class:

    Base1 base1; // sizeof(base1) == 4 bytes
    MultipleInheritance multiple; // sizeof(multiple) == 8 bytes

    I meant, supposing the vtable implementation is being used (by far the
    most used), we are adding to any object or instance of the class an
    extra pointer.

    About the diamond problem, still complex, can be solved with the
    virtual inheritance and the scope resolution operator:

    multiple->Base1::func();
    multiple->Base2::func();

    So, my question was what is the benefit or making that classes
    abstract or pure virtual in languages like C#, D...
    We have the same extra pointers in the object, haven't we?

    Cheers, lieve
     
    Aitor, Sep 22, 2012
    #3
  4. lieve again

    lieve again Guest

    > > So with multiple inheritance we ends with big classes because of the
    > > need of extra virtual pointers,

    >
    > You are mixing classes with objects here. You should stop. Just take few steps
    > back and learn basics. What is class what is object and what is the difference.


    First thank you for the answers.

    I didn't mean classes but object or the instances of the class:

    Base1 base1; // sizeof(base1) == 4 bytes
    MultipleInheritance multiple; // sizeof(multiple) == 8 bytes

    I meant, supposing the vtable implementation is being used (by far
    the
    most used), we are adding to any object or instance of the class an
    extra pointer.

    About the diamond problem, still complex, can be solved with the
    virtual inheritance and the scope resolution operator:

    multiple->Base1::func();
    multiple->Base2::func();

    So, my question was what is the benefit or making that classes
    abstract or pure virtual in languages like C#, D...
    We have the same extra pointers in the object, haven't we?

    Cheers, lieve
     
    lieve again, Sep 22, 2012
    #4
  5. lieve again

    Öö Tiib Guest

    On Saturday, September 22, 2012 1:39:53 PM UTC+3, Aitor wrote:
    > I didn't mean classes but object or the instances of the class:
    >
    > Base1 base1; // sizeof(base1) == 4 bytes
    > MultipleInheritance multiple; // sizeof(multiple) == 8 bytes
    >
    > I meant, supposing the vtable implementation is being used (by far the
    > most used), we are adding to any object or instance of the class an
    > extra pointer.
    >
    > About the diamond problem, still complex, can be solved with the
    > virtual inheritance and the scope resolution operator:
    >
    > multiple->Base1::func();
    > multiple->Base2::func();
    >
    > So, my question was what is the benefit or making that classes
    > abstract or pure virtual in languages like C#, D...
    > We have the same extra pointers in the object, haven't we?


    Benefit is that you can derive a class only from one class that actually
    implements something. So one of ways to achieve serious complexity (make deep
    and wide inheritance hierarchies) is removed from your toolset. As result you
    can not burn the brains of novice maintainer who has to work on your code with
    the class hierarchy.

    Most modern programming languages try to be simple to use and to understand.
    C++ trys to be efficient and feature-rich instead. C++ can be also simple
    to use and to understand but then you have to agree with your team that
    you follow certain idioms strictly and avoid using lot of things that are
    legal C++ by standard.
     
    Öö Tiib, Sep 22, 2012
    #5
  6. On 9/20/12 8:59 AM, lieve again wrote:
    >
    > So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance) == 4
    > bytes (only one virtual pointer)
    > but sizeof(MultipleInheritance) == 8 bytes
    > if class MultipleInheritance would inherit from another Base3, the
    > size would be 12 bytes and so on.
    >

    And this should be expected as we would expect
    sizeof(MultipleInheritance) = sizeof(Base1) + sizeof(Base2) +
    sizeof(stuff added in MultipleInheritance)

    as we would normally expect that each of the base classes be fully
    represented within the derived class so it is easy to treat the derived
    class as if it was any of its base classes. (There is an exception for
    empty base classes which must have a sizeof > 0 as a class by itself,
    but might not take any extra room when derived from.)

    > So with multiple inheritance we ends with big classes because of the
    > need of extra virtual pointers,
    > to avoid that, almost every language doesn't implement multiple
    > inheritance but Interfaces, where one
    > can only inherit more than one class but being that classes abstract
    > or pure virtual, like:
    >
    > class Base1{
    > virtual void func() = 0;
    > virtual void func3();
    > };
    >
    > class MixedClass : public NormalClass implements Base1, Base2,
    > Base...
    >
    > My question is: Don't we have the same implementation problem as in C+
    > +? Because even being these classes
    > abstract, they need a virtual pointer. Why do they impose that rule
    > in languages like C#, Java, D...?
    > Someone know the reason?
    >


    When inheriting from Interfaces, the difference is that the Interface
    never needs to exist as a discrete object, so there isn't a need to save
    a vtable pointer for each Interface. The Interface routine likely need a
    pointer into the base class vtable to the vtable for that interface, but
    that should be computable from the object normal vtable pointer.

    The main reason many languages don't implement multiple inheritance (but
    maybe Interfaces) is NOT object size, but language a program complexity.
     
    Richard Damon, Sep 22, 2012
    #6
  7. lieve again

    Stuart Guest

    On 9/20/12 "lieve again" wrote:
    > Hi!
    > I have a question regarding the implementation of the multiple
    > inheritance in C++.
    > As far as I know, the implementation problem of multiple inheritance
    > (one of them) in every programming language is the need for an extra
    > pointer for each new inherited class.
    > For example:
    > class Base1{
    > // hidden Base1_vtr = &Base1_vtbl[0]
    > virtual void func();
    > virtual void func2();
    > virtual void func3();
    > };
    > class Base2{
    > // hidden Base2_vtr = &Base2_vtbl[0]
    > virtual base2func();
    > virtual base2func2();
    > };
    >
    > class NormalInheritance : public Base1 {
    > // hidden Base1_vtr = &Base1_vtbl[0]
    > virtual void anotherFunc();
    > };
    >
    > class MultipleInheritance : public Base1, Base2 {
    > // hidden Base1_vtr = &Base1_vtbl[0]
    > // hidden Base2_vtr = &Base2_vtbl[0]
    > virtual void anotherFunc();
    > };
    >
    > So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance) == 4
    > bytes (only one virtual pointer)
    > but sizeof(MultipleInheritance) == 8 bytes
    > if class MultipleInheritance would inherit from another Base3, the
    > size would be 12 bytes and so on.


    That's right.


    > So with multiple inheritance we ends with big classes because of the
    > need of extra virtual pointers,
    > to avoid that, almost every language doesn't implement multiple
    > inheritance but Interfaces,



    I don't think that this is the reason why the implementors of other
    languages chose a single-inheritance approach. In my opinion it is the
    complexity of a multiple-inheritance language that puts off a lot of
    low-end programmers, but these programmers are the bulk of the
    industry's employees.


    > where one
    > can only inherit more than one class but being that classes abstract
    > or pure virtual, like:
    >
    > class Base1{
    > virtual void func() = 0;
    > virtual void func3();
    > };
    >
    > class MixedClass : public NormalClass implements Base1, Base2,
    > Base...
    >
    > My question is: Don't we have the same implementation problem as in C+
    > +?



    Yes, single inheritance languages that are implemented using vtables
    still suffer from this problem.


    > Because even being these classes
    > abstract, they need a virtual pointer. Why do they impose that rule
    > in languages like C#, Java, D...?
    > Someone know the reason?



    We can only guess. Unfortunately, there is no single person that has
    perceived the Java programming language, so we cannot ask somebody (in
    contrast to Mr. Stroustrup, who, although rarely, can be seen in this
    newsgroup).

    Regards,
    Stuart
     
    Stuart, Sep 23, 2012
    #7
  8. lieve again

    lieve again Guest

    On 23 sep, 16:32, Stuart <> wrote:
    > On 9/20/12 "lieve again" wrote:
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > > Hi!
    > > I have a question regarding the implementation of the multiple
    > > inheritance in C++.
    > > As far as I know, the implementation problem of multiple inheritance
    > > (one of them) in every programming language is the need for an extra
    > > pointer for each new inherited class.
    > > For example:
    > > class Base1{
    > >   // hidden Base1_vtr = &Base1_vtbl[0]
    > >   virtual void func();
    > >   virtual void func2();
    > >   virtual void func3();
    > >   };
    > >   class Base2{
    > >    // hidden Base2_vtr = &Base2_vtbl[0]
    > >   virtual base2func();
    > >   virtual base2func2();
    > >   };

    >
    > >   class NormalInheritance : public Base1 {
    > >   // hidden Base1_vtr = &Base1_vtbl[0]
    > >   virtual void anotherFunc();
    > >   };

    >
    > >   class MultipleInheritance : public Base1, Base2 {
    > >   // hidden Base1_vtr = &Base1_vtbl[0]
    > >   // hidden Base2_vtr = &Base2_vtbl[0]
    > > virtual void anotherFunc();
    > > };

    >
    > > So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance)== 4
    > > bytes (only one virtual pointer)
    > > but sizeof(MultipleInheritance) == 8 bytes
    > > if class MultipleInheritance would inherit from another Base3, the
    > > size would be 12 bytes and so on.

    >
    > That's right.
    >
    > > So with multiple inheritance we ends with big classes because of the
    > > need of extra virtual pointers,
    > > to avoid that, almost every language doesn't implement multiple
    > > inheritance but Interfaces,

    >
    > I don't think that this is the reason why the implementors of other
    > languages chose a single-inheritance approach. In my opinion it is the
    > complexity of a multiple-inheritance language that puts off a lot of
    > low-end programmers, but these programmers are the bulk of the
    > industry's employees.
    >
    > > where one
    > > can only inherit more than one class but being that classes abstract
    > > or pure virtual, like:

    >
    > > class Base1{
    > >   virtual void func() = 0;
    > >   virtual void func3();
    > >   };

    >
    > >   class MixedClass : public NormalClass implements Base1, Base2,
    > > Base...

    >
    > >   My question is: Don't we have the same implementation problem as inC+
    > > +?

    >
    > Yes, single inheritance languages that are implemented using vtables
    > still suffer from this problem.
    >
    >  > Because even being these classes
    >
    > >   abstract, they need a virtual pointer. Why do they impose that rule
    > > in languages like C#, Java, D...?
    > >   Someone know the reason?

    >
    > We can only guess. Unfortunately, there is no single person that has
    > perceived the Java programming language, so we cannot ask somebody (in
    > contrast to Mr. Stroustrup, who, although rarely, can be seen in this
    > newsgroup).
    >
    > Regards,
    > Stuart


    Ok, so the main reason for not implementing multiple inheritance
    (without workarounds) is the complexity added to the programmers
    (learning curve) and to the compiler developers (diamond
    problem, ...). I thought maybe making the interfaces pure virtual,
    there was a way to avoid the extra vpointers and I wanted to know how.
    Then if I start adding pure virtual classes to impose the derived
    classes with some kind of features like:
    class Derived : implements Readable, Writeable, Comparable,
    Convertible ...
    regardless of the programming language, we are ending with instances
    of the derived classes having 20 bytes or more even being those
    classes with no members or empty. It is good to know.

    Regards,
    lieve
     
    lieve again, Sep 23, 2012
    #8
  9. lieve again

    Öö Tiib Guest

    On Sunday, 23 September 2012 23:40:42 UTC+3, lieve again wrote:
    > Ok, so the main reason for not implementing multiple inheritance
    > (without workarounds) is the complexity added to the programmers
    > (learning curve) and to the compiler developers (diamond
    > problem, ...).


    Yes, that is the reason. Also the solution, virtual inheritance is
    not too efficient nor simple.

    > I thought maybe making the interfaces pure virtual,
    > there was a way to avoid the extra vpointers and I wanted to know how.
    > Then if I start adding pure virtual classes to impose the derived
    > classes with some kind of features like:
    > class Derived : implements Readable, Writeable, Comparable,
    > Convertible ...
    > regardless of the programming language, we are ending with instances
    > of the derived classes having 20 bytes or more even being those
    > classes with no members or empty. It is good to know.


    The bytes actually are cheap these days ... unless you write for some 8 bit
    controller. On common platforms most of the memory goes into visuals
    and sounds and helper texts and other massive data like that. Couple of bytes
    for vtables of object that manages such data are usually not worth talking
    about.

    OTOH on the 8-bit controllers where you really count bytes you do not have
    much need for such large class hierarchy anyway.
     
    Öö Tiib, Sep 23, 2012
    #9
  10. lieve again

    lieve again Guest

    On 23 Sep., 23:25, Öö Tiib <> wrote:
    > On Sunday, 23 September 2012 23:40:42 UTC+3, lieve again  wrote:
    > > Ok, so the main reason for not implementing multiple inheritance
    > > (without workarounds) is the complexity added to the programmers
    > > (learning curve) and to the compiler developers (diamond
    > > problem, ...).

    >
    > Yes, that is the reason. Also the solution, virtual inheritance is
    > not too efficient nor simple.
    >
    > > I thought maybe making the interfaces pure virtual,
    > > there was a way to avoid the extra vpointers and I wanted to know how.
    > > Then if I start adding pure virtual classes to impose the derived
    > > classes with some kind of features like:
    > > class Derived : implements Readable, Writeable, Comparable,
    > > Convertible ...
    > > regardless of the programming language, we are ending with instances
    > > of the derived classes having 20 bytes or more even being those
    > > classes with no members or empty. It is good to know.

    >
    > The bytes actually are cheap these days ... unless you write for some 8 bit
    > controller. On common platforms most of the memory goes into visuals
    > and sounds and helper texts and other massive data like that. Couple of bytes
    > for vtables of object that manages such data are usually not worth talking
    > about.
    >
    > OTOH on the 8-bit controllers where you really count bytes you do not have
    > much need for such large class hierarchy anyway.


    Ok, so its a problem suffered from all the actual programming
    languages, I think it could be a kind of limitation to obtain so big
    objects, but its so.
    Maybe the way to impose some kind of properties or functions to a
    class without the vpointers replication penalty is the concepts
    extension of C++11.

    Regards,
     
    lieve again, Sep 26, 2012
    #10
  11. lieve again

    Stuart Guest

    On 9/23/12 "lieve again" wrote:
    [snip]
    > I thought maybe making the interfaces pure virtual,
    > there was a way to avoid the extra vpointers and I wanted to know how.
    > Then if I start adding pure virtual classes to impose the derived
    > classes with some kind of features like:
    > class Derived : implements Readable, Writeable, Comparable,
    > Convertible ...
    > regardless of the programming language, we are ending with instances
    > of the derived classes having 20 bytes or more even being those
    > classes with no members or empty. It is good to know.


    There is one way to avoid object bloat, but you are not going to like it :)


    The following code uses a hand-made vtable substitute. For that reason I
    had to introduce a special pointer type which stores the class's ID
    together with the pointer to the object. This class ID is assigned by
    hand, so that this scheme will only work for class hierarchies that will
    not get extended (if you want to use this for extendable class
    hierarchies, you'd have to use growable look-up table instead of a fixed
    array of method pointers, but then you had better use objective-C++).

    Note that the base class Base in my example must not contain any virtual
    methods, or else the class Derived will get bloated again. Of course,
    this makes the code really awfull to look at. However, most of it may
    get generated by a some clever pre-processor magic.

    Since the full code may cause some shock, I'll give a short summary:

    class Base {
    public:
    void foo () {std::cout << "Base::foo\n";}
    BasePtr operator& ();
    };

    class Derived : public Base {
    public:
    void foo () {std::cout << "Derived::foo\n";}
    DerivedPtr operator&();
    }

    Base1Ptr, Base2Ptr and DerivedPtr and those special pointer types that
    allow us to do the following:

    void invokeFooVirtually (const BasePtr& b) {
    b.foo();
    }

    int main () {
    Base b;
    Derived d;
    invokeFooVirtually(&b);
    invokeFooVirtually(&d);
    }

    will print:

    Base1::foo
    Derived::foo

    There is a price you have to pay: The pointer types are now twice as
    large (the xxxPtr contains not only the raw pointer but also the class's
    ID). Furthermore the cast from DerivedPtr to BasePtr has to call the
    cast operator of DerivedPtr.



    A compilable example with two base classes and some members:

    #include <iostream>

    class Base1Ptr {
    protected:
    static const int classID;
    class Base1* ptr;
    int objectID;
    public:
    Base1Ptr (class Base1* ptr, int objectID = classID)
    : ptr(ptr), objectID(objectID) {}
    void foo () const;
    };
    const int Base1Ptr::classID = 0;

    class Base1 {
    protected:
    int base1Int;
    public:
    Base1 (int i) : base1Int(i / 2) {}
    void foo () { std::cout << "Base1::foo with base1Int = "
    << base1Int << "\n";}
    Base1Ptr operator& () {return Base1Ptr(this);}
    };


    class Base2Ptr {
    protected:
    static const int classID;
    class Base2* ptr;
    int objectID;
    public:
    Base2Ptr (class Base2* ptr, int objectID = classID)
    : ptr(ptr), objectID(objectID) {}
    void bar () const;
    };
    const int Base2Ptr::classID = 0;

    class Base2 {
    protected:
    int base2Int;
    public:
    Base2(int i) : base2Int(i * 2){}
    void bar () { std::cout << "Base2::bar with base2Int = "
    << base2Int << "\n";}
    Base2Ptr operator* ();
    };




    class DerivedPtr {
    class Derived* ptr;
    int objectID;
    public:
    DerivedPtr (class Derived* ptr, int objectID)
    : ptr(ptr), objectID(objectID) {}
    operator Base1Ptr();
    operator Base2Ptr();
    };


    class Derived : public Base1, public Base2 {
    static const int classID;
    public:
    Derived (int i) : Base1(i), Base2(i){}
    void foo () { std::cout << "Derived::foo with base1Int = "
    << base1Int << "and base2Int = "
    << base2Int << "\n";}
    void bar () { std::cout << "Derived::bar\n";}
    DerivedPtr operator&() {return DerivedPtr(this,classID);}
    };

    const int Derived::classID = 1; // std::max(Base1::classID,
    // Base2::classID) + 1;
    DerivedPtr::eek:perator Base1Ptr() {return Base1Ptr(ptr, objectID);}
    DerivedPtr::eek:perator Base2Ptr() {return Base2Ptr(ptr, objectID);}



    // Base1Ptr uses the following table to look up
    // the correct member function.
    typedef void (Base1::*FooPtr)(void);
    FooPtr fooVTable[] = {&Base1::foo, (FooPtr)&Derived::foo};
    void Base1Ptr::foo () const
    {
    FooPtr targetFooFunction = fooVTable[objectID];
    (ptr->*targetFooFunction)();
    }

    // The same goes for Base2.
    typedef void (Base2::*BarPtr)(void);
    BarPtr barVTable[] = {&Base2::bar, (BarPtr)&Derived::bar};
    void Base2Ptr::bar () const
    {
    BarPtr targetFooFunction = barVTable[objectID];
    (ptr->*targetFooFunction)();
    }

    // Note that the cast of the addresses of members of Derived
    // to addresses of members of Base results in UB.
    // However, the results are pretty much as expected.




    void invokeFoo (Base1* b) {
    b->foo();
    }

    void invokeFooVirtually (const Base1Ptr& b) {
    b.foo();
    }

    void invokeBarVirtually (const Base2Ptr& b) {
    b.bar();
    }

    int main () {
    Base1 b(3);
    Derived d(42);

    std::cout << "sizeof(Base1) == " << sizeof(Base1) << "\n";
    std::cout << "sizeof(Derived) == " << sizeof(Derived) << "\n";
    std::cout << "sizeof(Base1*) == " << sizeof(Base1*) << "\n";
    std::cout << "sizeof(Base1Ptr) == " << sizeof(Base1Ptr) << "\n";
    std::cout << "sizeof(Derived*) == " << sizeof(Derived*) << "\n";
    std::cout << "sizeof(DerivedPtr) == " << sizeof(DerivedPtr) << "\n";

    invokeFooVirtually(&b);
    invokeFooVirtually(&d);
    invokeBarVirtually(&d);
    }

    Regards,
    Stuart
     
    Stuart, Sep 26, 2012
    #11
  12. lieve again

    lieve again Guest

    On 26 sep, 23:39, Stuart <> wrote:
    > On 9/23/12 "lieve again" wrote:
    > [snip]
    >
    > > I thought maybe making the interfaces pure virtual,
    > > there was a way to avoid the extra vpointers and I wanted to know how.
    > > Then if I start adding pure virtual classes to impose the derived
    > > classes with some kind of features like:
    > > class Derived : implements Readable, Writeable, Comparable,
    > > Convertible ...
    > > regardless of the programming language, we are ending with instances
    > > of the derived classes having 20 bytes or more even being those
    > > classes with no members or empty. It is good to know.

    >
    > There is one way to avoid object bloat, but you are not going to like it :)
    >
    > The following code uses a hand-made vtable substitute. For that reason I
    > had to introduce a special pointer type which stores the class's ID
    > together with the pointer to the object. This class ID is assigned by
    > hand, so that this scheme will only work for class hierarchies that will
    > not get extended (if you want to use this for extendable class
    > hierarchies, you'd have to use growable look-up table instead of a fixed
    > array of method pointers, but then you had better use objective-C++).
    >
    > Note that the base class Base in my example must not contain any virtual
    > methods, or else the class Derived will get bloated again. Of course,
    > this makes the code really awfull to look at. However, most of it may
    > get generated by a some clever pre-processor magic.
    >
    > Since the full code may cause some shock, I'll give a short summary:
    >
    > class Base {
    > public:
    >      void foo () {std::cout << "Base::foo\n";}
    >      BasePtr operator& ();
    >
    > };
    >
    > class Derived : public Base {
    > public:
    >      void foo () {std::cout << "Derived::foo\n";}
    >      DerivedPtr operator&();
    >
    > }
    >
    > Base1Ptr, Base2Ptr and DerivedPtr and those special pointer types that
    > allow us to do the following:
    >
    > void invokeFooVirtually (const BasePtr& b) {
    >      b.foo();
    >
    > }
    >
    > int main () {
    >      Base b;
    >      Derived d;
    >      invokeFooVirtually(&b);
    >      invokeFooVirtually(&d);
    >
    > }
    >
    > will print:
    >
    > Base1::foo
    > Derived::foo
    >
    > There is a price you have to pay: The pointer types are now twice as
    > large (the xxxPtr contains not only the raw pointer but also the class's
    > ID). Furthermore the cast from DerivedPtr to BasePtr has to call the
    > cast operator of DerivedPtr.
    >
    > A compilable example with two base classes and some members:
    >
    > #include <iostream>
    >
    > class Base1Ptr {
    > protected:
    >      static const int classID;
    >      class Base1* ptr;
    >      int objectID;
    > public:
    >      Base1Ptr (class Base1* ptr, int objectID = classID)
    >        : ptr(ptr), objectID(objectID) {}
    >      void foo () const;};
    >
    > const int Base1Ptr::classID = 0;
    >
    > class Base1 {
    > protected:
    >      int base1Int;
    > public:
    >      Base1 (int i) : base1Int(i / 2) {}
    >      void foo () { std::cout << "Base1::foo with base1Int = "
    >                              << base1Int <<"\n";}
    >      Base1Ptr operator& () {return Base1Ptr(this);}
    >
    > };
    >
    > class Base2Ptr {
    > protected:
    >      static const int classID;
    >      class Base2* ptr;
    >      int objectID;
    > public:
    >      Base2Ptr (class Base2* ptr, int objectID = classID)
    >        : ptr(ptr), objectID(objectID) {}
    >      void bar () const;};
    >
    > const int Base2Ptr::classID = 0;
    >
    > class Base2 {
    > protected:
    >      int base2Int;
    > public:
    >      Base2(int i) : base2Int(i * 2){}
    >      void bar () { std::cout << "Base2::bar with base2Int = "
    >                              << base2Int <<"\n";}
    >      Base2Ptr operator* ();
    >
    > };
    >
    > class DerivedPtr {
    >      class Derived* ptr;
    >      int objectID;
    > public:
    >      DerivedPtr (class Derived* ptr, int objectID)
    >        : ptr(ptr), objectID(objectID) {}
    >      operator Base1Ptr();
    >      operator Base2Ptr();
    >
    > };
    >
    > class Derived : public Base1, public Base2 {
    >      static const int classID;
    > public:
    >      Derived (int i) : Base1(i), Base2(i){}
    >      void foo () { std::cout << "Derived::foo with base1Int = "
    >                              << base1Int <<"and base2Int = "
    >                              << base2Int <<"\n";}
    >      void bar () { std::cout << "Derived::bar\n";}
    >      DerivedPtr operator&() {return DerivedPtr(this,classID);}
    >
    > };
    >
    > const int Derived::classID = 1; // std::max(Base1::classID,
    >                                  //         Base2::classID) + 1;
    > DerivedPtr::eek:perator Base1Ptr() {return Base1Ptr(ptr, objectID);}
    > DerivedPtr::eek:perator Base2Ptr() {return Base2Ptr(ptr, objectID);}
    >
    > // Base1Ptr uses the following table to look up
    > // the correct member function.
    > typedef void (Base1::*FooPtr)(void);
    > FooPtr fooVTable[] = {&Base1::foo, (FooPtr)&Derived::foo};
    > void Base1Ptr::foo () const
    > {
    >      FooPtr targetFooFunction = fooVTable[objectID];
    >      (ptr->*targetFooFunction)();
    >
    > }
    >
    > // The same goes for Base2.
    > typedef void (Base2::*BarPtr)(void);
    > BarPtr barVTable[] = {&Base2::bar, (BarPtr)&Derived::bar};
    > void Base2Ptr::bar () const
    > {
    >      BarPtr targetFooFunction = barVTable[objectID];
    >      (ptr->*targetFooFunction)();
    >
    > }
    >
    > // Note that the cast of the addresses of members of Derived
    > // to addresses of members of Base results in UB.
    > // However, the results are pretty much as expected.
    >
    > void invokeFoo (Base1* b) {
    >      b->foo();
    >
    > }
    >
    > void invokeFooVirtually (const Base1Ptr& b) {
    >      b.foo();
    >
    > }
    >
    > void invokeBarVirtually (const Base2Ptr& b) {
    >      b.bar();
    >
    > }
    >
    > int main () {
    >      Base1 b(3);
    >      Derived d(42);
    >
    >      std::cout << "sizeof(Base1) == " << sizeof(Base1) << "\n";
    >      std::cout << "sizeof(Derived) == " << sizeof(Derived) << "\n";
    >      std::cout << "sizeof(Base1*) == " << sizeof(Base1*) << "\n";
    >      std::cout << "sizeof(Base1Ptr) == " << sizeof(Base1Ptr) <<"\n";
    >      std::cout << "sizeof(Derived*) == " << sizeof(Derived*) <<"\n";
    >      std::cout << "sizeof(DerivedPtr) == " << sizeof(DerivedPtr) << "\n";
    >
    >      invokeFooVirtually(&b);
    >      invokeFooVirtually(&d);
    >      invokeBarVirtually(&d);
    >
    > }
    >
    > Regards,
    > Stuart


    Interesting (and complex) example. I think, I have understood it, its
    a way to avoid the extra vpointers in class by make the base pointers
    "fatter". Probably some programming language have implemented multiple
    inheritance that way.


    Looking the code, another way to do it were:

    // typedef void (Base1::*FooPtr)(void);
    // FooPtr fooVTable[] = {&Base1::foo, (FooPtr)&Derived::foo};
    void Base1Ptr::foo () const
    {
    FooPtr targetFooFunction = fooVTable[objectID];
    (ptr->*targetFooFunction)();

    if(this->objectID != 0){
    Derived* derived = static_cast<Derived*>(ptr);
    derived->foo();
    }else // not derived, call it normally
    ptr->foo();
    }

    With the objectID we already know the true class of the base pointer,
    so we could simply convert it safely to the derived class, in that way
    we can avoid the pointer to member functions which they are a little
    bit complicated. Instead we can made a table of int like an index to
    know the true class or at least if we need to use virtual functions,
    something like:
    enum Index { BASE1 = 0, BASE2 = 1, BASE3 = 2, DERIVED = 3};

    an depending of the table answer, convert it to the right class.

    We could store the object id directly in the Base1, and forget about
    the BasePtr and the conversion operators, but this would made the base
    classes bigger, so the Base1 approach its better: we make the pointer
    fatter only if an conversion from the derived class to a base class
    take place.

    Thanks!!
     
    lieve again, Sep 30, 2012
    #12
  13. lieve again

    Cholo Lennon Guest

    On 09/22/2012 03:10 PM, Richard Damon wrote:
    > On 9/20/12 8:59 AM, lieve again wrote:
    >>
    >> So, sizeof(Base1) == sizeof(Base2) == sizeof(NormalInheritance) == 4
    >> bytes (only one virtual pointer)
    >> but sizeof(MultipleInheritance) == 8 bytes
    >> if class MultipleInheritance would inherit from another Base3, the
    >> size would be 12 bytes and so on.
    >>

    > And this should be expected as we would expect
    > sizeof(MultipleInheritance) = sizeof(Base1) + sizeof(Base2) +
    > sizeof(stuff added in MultipleInheritance)
    >
    > as we would normally expect that each of the base classes be fully
    > represented within the derived class so it is easy to treat the derived
    > class as if it was any of its base classes. (There is an exception for
    > empty base classes which must have a sizeof > 0 as a class by itself,
    > but might not take any extra room when derived from.)
    >
    >> So with multiple inheritance we ends with big classes because of the
    >> need of extra virtual pointers,
    >> to avoid that, almost every language doesn't implement multiple
    >> inheritance but Interfaces, where one
    >> can only inherit more than one class but being that classes abstract
    >> or pure virtual, like:
    >>
    >> class Base1{
    >> virtual void func() = 0;
    >> virtual void func3();
    >> };
    >>
    >> class MixedClass : public NormalClass implements Base1, Base2,
    >> Base...
    >>
    >> My question is: Don't we have the same implementation problem as in C+
    >> +? Because even being these classes
    >> abstract, they need a virtual pointer. Why do they impose that rule
    >> in languages like C#, Java, D...?
    >> Someone know the reason?
    >>

    >
    > When inheriting from Interfaces, the difference is that the Interface
    > never needs to exist as a discrete object, so there isn't a need to save
    > a vtable pointer for each Interface. The Interface routine likely need a
    > pointer into the base class vtable to the vtable for that interface, but
    > that should be computable from the object normal vtable pointer.
    >


    That's why VC++ has an extension, __declspec(novtable) to mark abstract
    classes intended to be used as interfaces.

    > The main reason many languages don't implement multiple inheritance (but
    > maybe Interfaces) is NOT object size, but language a program complexity.
    >



    --
    Cholo Lennon
    Bs.As.
    ARG
     
    Cholo Lennon, Oct 1, 2012
    #13
  14. On 9/30/12 11:04 PM, Cholo Lennon wrote:
    > On 09/22/2012 03:10 PM, Richard Damon wrote:
    >>
    >> When inheriting from Interfaces, the difference is that the Interface
    >> never needs to exist as a discrete object, so there isn't a need to save
    >> a vtable pointer for each Interface. The Interface routine likely need a
    >> pointer into the base class vtable to the vtable for that interface, but
    >> that should be computable from the object normal vtable pointer.
    >>

    >
    > That's why VC++ has an extension, __declspec(novtable) to mark abstract
    > classes intended to be used as interfaces.
    >


    Unfortunately, __declspec(novtable) does NOT remove the vtable from the
    object, but the removes the instruction to initialize the pointer from
    the constructor, and thus likely removes the vtable itself (which occurs
    only once per class, not once per object, so only a small savings). The
    class object will still need to have a vtablepointer in it, so members
    of that class can find their virtual functions.

    The "interface" class members still need to be passed a "this" object of
    the appropriate type, and that object needs to have a vtablepointer in
    it to find the virtual functions. The problem here is that the "inteface
    class" isn't something different than a regular class.

    The typical difference with real interface classes in other languages is
    that those classes don't take a pointer to the interface class object,
    but to the full object, they "know" that they are going to be used as a
    "mixin" class. They will normally take as their calling sequence API a
    pointer to the object (not their "sub object", as those language tend
    not to define a thing called a "sub object", and a pointer to the
    section of the vtable which acts as the vtable for interface. Since
    interfaces normally can not have member variables, only functions, there
    is no need to have a pointer to that.
     
    Richard Damon, Oct 1, 2012
    #14
  15. lieve again

    Pavel Guest

    lieve again wrote:
    > On 23 Sep., 23:25, Öö Tiib <> wrote:
    >> On Sunday, 23 September 2012 23:40:42 UTC+3, lieve again wrote:
    >>> Ok, so the main reason for not implementing multiple inheritance
    >>> (without workarounds) is the complexity added to the programmers
    >>> (learning curve) and to the compiler developers (diamond
    >>> problem, ...).

    >>
    >> Yes, that is the reason. Also the solution, virtual inheritance is
    >> not too efficient nor simple.
    >>
    >>> I thought maybe making the interfaces pure virtual,
    >>> there was a way to avoid the extra vpointers and I wanted to know how.
    >>> Then if I start adding pure virtual classes to impose the derived
    >>> classes with some kind of features like:
    >>> class Derived : implements Readable, Writeable, Comparable,
    >>> Convertible ...
    >>> regardless of the programming language, we are ending with instances
    >>> of the derived classes having 20 bytes or more even being those
    >>> classes with no members or empty. It is good to know.

    >>
    >> The bytes actually are cheap these days ... unless you write for some 8 bit
    >> controller. On common platforms most of the memory goes into visuals
    >> and sounds and helper texts and other massive data like that. Couple of bytes
    >> for vtables of object that manages such data are usually not worth talking
    >> about.
    >>
    >> OTOH on the 8-bit controllers where you really count bytes you do not have
    >> much need for such large class hierarchy anyway.

    >
    > Ok, so its a problem suffered from all the actual programming
    > languages, I think it could be a kind of limitation to obtain so big
    > objects, but its so.
    > Maybe the way to impose some kind of properties or functions to a
    > class without the vpointers replication penalty is the concepts
    > extension of C++11.
    >
    > Regards,
    >

    I think the above is not accurate. 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.

    Languages with single inheritance can assign a single offset from the start of
    the virtual table of the most-derived class of an object to the start of the
    slice of that class' virtual table correspondent to the virtual table of any of
    its bases. This effectively means that any class in such a language can have a
    single virtual table and the objects of the most derived class and the
    correspondent objects of all its base classes can have a single address.

    Complicating C++ specification by introducing special kind of classes that would
    be forbidden from being used as base classes in multiple inheritance (or, even
    more complex but more rewarding as well -- the classes that cannot be used as
    other-than-first base classes in multiple inheritance) could eliminate this
    penalty for the language users who do not use multiple inheritance.

    C++ does not have a chance of assigning any virtual function once defined in a
    class a single offset in a virtual table; therefore, it has to have multiple
    virtual tables. As it is, that is without the complication mentioned above, C++
    can not let its compiler know at a virtual call site that the call is on the
    object that is the first base of the most-derived class; hence the necessity to
    always read and apply the offset at run-time.

    Languages with single inheritance and interfaces still have same performance
    advantages as languages without interfaces for the classes that do not implement
    interfaces (that is, they live to the promise of not imposing cost of a feature
    on the code that does not use it better than C++ does). For a class that does
    implement interfaces, the implementations have a choice of either avoiding space
    cost but making calls by interface significantly more expensive or adding
    pointers to individual "interface virtual tables" to the layout of an object of
    such a class and having calls by an interface only one indirection more
    expensive than regular virtual calls. I believe Java 1.0 took the first path and
    Java 1.1 and all its further versions took the second.

    -Pavel
     
    Pavel, Oct 7, 2012
    #15
  16. On 10/7/12 4:57 PM, Pavel wrote:

    > I think the above is not accurate. 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.
    >


    This is incorrect. It is possible to setup the virtual table so that
    virtual functions based on the 1st base class make direct calls to the
    destination functions (since no pointer adjustment is needed) but if a
    class is the later base, there are actually 2 tables of pointers to the
    functions, one pointer to by the base sub object, and a second one, part
    of the table pointed to by the 1st base object. These two different
    tables point to different points, one being to a "thunk" that adjusts
    the this pointer, and the other which doesn't.

    Base class functions, which see the this pointer as the later base
    object, only have the base pointer in that base object, this version
    does not adjust the pointer if the function was last defined under that
    base object, but if the function has been overridden since the multiple
    inheritance, it is a thunk which adjust the this pointer and then goes
    to the override.

    Functions after the multiple inheritance use the pointer in the first
    base class, which does the reverse, having a thunk if the function was
    last overridden before the multiple inheritance, and a direct connection
    if after.


    Zero cost unless there is multiple inheritance, and then only for calls
    to functions that do require adjusting of the this pointer.
     
    Richard Damon, Oct 8, 2012
    #16
  17. lieve again

    Stuart Guest

    [the OP, "lieve again", observed the problem that multiple inheritance
    of either interface or non-interface classes leads to object bloat due
    to the necessity to stuff objects with multiple vtables]

    lieve again wrote:
    >> Ok, so its a problem suffered from all the actual programming
    >> languages, I think it could be a kind of limitation to obtain so big
    >> objects, but its so.
    >> Maybe the way to impose some kind of properties or functions to a
    >> class without the vpointers replication penalty is the concepts
    >> extension of C++11.


    On 10/7/12 Pavel wrote:
    > I think the above is not accurate. 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


    What's the call target? Never heard this term.

    > 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 don't get what you mean. Can you give an example?

    > Languages with single inheritance can assign a single offset from the
    > start of the virtual table of the most-derived class of an object to the
    > start of the slice of that class' virtual table correspondent to the
    > virtual table of any of its bases.


    Yeah, for C++ this offset will always be zero.

    > This effectively means that any class
    > in such a language can have a single virtual table and the objects of
    > the most derived class and the correspondent objects of all its base
    > classes can have a single address.


    Right. So casting a Derived* pointer to a Base* pointer for
    single-inheritance chains will always be a noop under C++. I don't see
    any kind of performance penalty.

    [snip]

    > C++ does not have a chance of assigning any virtual function once
    > defined in a class a single offset in a virtual table; therefore, it has
    > to have multiple virtual tables. As it is, that is without the
    > complication mentioned above, C++ can not let its compiler know at a
    > virtual call site that the call is on the object that is the first base
    > of the most-derived class; hence the necessity to always read and apply
    > the offset at run-time.


    Which offset are you talking about? Can you give an example (preferably
    for the Intel architecture)?

    Regards,
    Stuart
     
    Stuart, Oct 8, 2012
    #17
  18. lieve again

    Pavel Guest

    Stuart wrote:
    >
    > [the OP, "lieve again", observed the problem that multiple inheritance of either
    > interface or non-interface classes leads to object bloat due to the necessity to
    > stuff objects with multiple vtables]
    >
    > lieve again wrote:
    >>> Ok, so its a problem suffered from all the actual programming
    >>> languages, I think it could be a kind of limitation to obtain so big
    >>> objects, but its so.
    >>> Maybe the way to impose some kind of properties or functions to a
    >>> class without the vpointers replication penalty is the concepts
    >>> extension of C++11.

    >
    > On 10/7/12 Pavel wrote:
    >> I think the above is not accurate. 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

    >
    > What's the call target? Never heard this term.


    The pointer to the base class on which a virtual function is called.

    >
    >> 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 don't get what you mean. Can you give an example?

    Because the compiler does not know (generally, Richard gave a good algo that can
    solve the issue -- but for cost) whether the base is the first base in the
    particular most-derived class, it has to read the offset of the sub-object in
    the object in which the virtual table is defined and subtract it from given
    pointer to the base class, at run-time.

    >
    >> Languages with single inheritance can assign a single offset from the
    >> start of the virtual table of the most-derived class of an object to the
    >> start of the slice of that class' virtual table correspondent to the
    >> virtual table of any of its bases.

    >
    > Yeah, for C++ this offset will always be zero.

    For calling a virtual functions defined in a class with multiple bases by base
    pointer, it will be zero only if the base is the first base (assuming without
    loss of generality that the compiler allocates first base at the lowest
    address); otherwise it will be something else. But, even if it is zero, it is
    not known in advance to the compiler so the code will still have to read that
    zero from memory and subtract it. That extra memory read can be relatively
    expensive (subtraction is usually not).

    >
    >> This effectively means that any class
    >> in such a language can have a single virtual table and the objects of
    >> the most derived class and the correspondent objects of all its base
    >> classes can have a single address.

    >
    > Right. So casting a Derived* pointer to a Base* pointer for single-inheritance
    > chains will always be a noop under C++.

    Right.
    I don't see any kind of performance
    > penalty.'

    The performance penalty will is incurred to cat Base* to Derived* which is what
    happens when you call Derived's overridden virtual function by pointer to Base.

    >
    > [snip]
    >
    >> C++ does not have a chance of assigning any virtual function once
    >> defined in a class a single offset in a virtual table; therefore, it has
    >> to have multiple virtual tables. As it is, that is without the
    >> complication mentioned above, C++ can not let its compiler know at a
    >> virtual call site that the call is on the object that is the first base
    >> of the most-derived class; hence the necessity to always read and apply
    >> the offset at run-time.

    >
    > Which offset are you talking about? Can you give an example (preferably for the
    > Intel architecture)?


    It's more compiler-specific than hardware-platform-specific. Imagine, compiler
    lays out objects with virtual functions by putting virtual table pointer before
    an object; and, for multiple inheritance, it places base sub-objects at the
    beginning of derived objects, in the order of its base specifier list. Then for
    these classes:

    // file b.h
    struct B { int b; virtual int getBOffset() const
    { return 0; }
    };
    // file b2.h
    struct B2 { int b2; };
    // file d.h
    #include "b.h"
    #include "b2.h"
    struct D: public B, public B2 { int d; virtual int getBOffset() const; };
    // file d.cpp
    #include "d.h"
    int D::getBOffset() const {
    return (const char*)this - (const char*)(const B*)this;
    // not zero, most likely sizeof(int)
    }
    // file d2.h
    #include "b.h"
    #include "b2.h"
    struct D2: public B2, public B { int d2; virtual int getBOffset() const; };
    // file d2.cpp
    #include "d2.h"
    int D2::getBOffset() const {
    return (const char*)this - (const char*)(const B*)this;
    // most likely, zero
    }
    // file x.cpp
    #include "d.h"
    #include "d2.h"
    B *createB1() { return new D1(); }
    B *createB2() { return new D2(); }
    // file client.cpp
    #include "b.h"
    #include "x.h"
    B *createB1();
    B *createB2();
    B *bPtr = createB1();
    int o1 = bPtr->getBOffset();
    B *bPtr2 = createB2();
    int o2 = bPtr2->getBOffset();

    Above, we know that to call bPtr2->getBOffset() (which is actually
    D2::getBOffset()) compiler does not have to subtract anything from *bPtr2, but
    compiler does not (D and D2 are not visible in client.cpp and neither are the
    definitions of createB1 or createB2. Therefore, compiler has to generate th code
    that reaches for the virtual table of bPtr2 and retrieve that zero (sometimes
    stored at some negative address in the virtual table and sometimes in other
    ways). As for the clever trick with double-virtual table and thunks described by
    Richard Damon to avoid that, it will work but it comes at some cost for calling
    virtual functions on non-second base and thus is not always employed (I will
    stop at it later in the answer to his post).

    >
    > Regards,
    > Stuart


    HTH
    -Pavel
     
    Pavel, Oct 10, 2012
    #18
  19. lieve again

    Pavel Guest

    Richard Damon wrote:
    > On 10/7/12 4:57 PM, Pavel wrote:
    >
    >> I think the above is not accurate. 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.
    >>

    >
    > This is incorrect. It is possible to setup the virtual table so that
    > virtual functions based on the 1st base class make direct calls to the
    > destination functions (since no pointer adjustment is needed) but if a
    > class is the later base, there are actually 2 tables of pointers to the
    > functions, one pointer to by the base sub object, and a second one, part
    > of the table pointed to by the 1st base object. These two different
    > tables point to different points, one being to a "thunk" that adjusts
    > the this pointer, and the other which doesn't.
    >
    > Base class functions, which see the this pointer as the later base
    > object, only have the base pointer in that base object, this version
    > does not adjust the pointer if the function was last defined under that
    > base object, but if the function has been overridden since the multiple
    > inheritance, it is a thunk which adjust the this pointer and then goes
    > to the override.
    >
    > Functions after the multiple inheritance use the pointer in the first
    > base class, which does the reverse, having a thunk if the function was
    > last overridden before the multiple inheritance, and a direct connection
    > if after.
    >
    >
    > Zero cost unless there is multiple inheritance, and then only for calls
    > to functions that do require adjusting of the this pointer.
    >

    True but thunk approach comes at higher cost for calling at non-first base of
    extra jump as compared with 'classic' approach. Extra jump in chunk is
    equivalent to at least one extra memory read (only to instruction instead of
    data cache) and some instruction decoding. The approach also somewhat increases
    memory usage with the second virtual table and thunks. But you are right about
    zero-cost if calls by non-first base are not used -- I completely forgot about
    thunk approach.

    -Pavel
     
    Pavel, Oct 10, 2012
    #19
  20. lieve again

    Stuart Guest

    >> 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
     
    Stuart, Oct 10, 2012
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Wildepiet
    Replies:
    0
    Views:
    1,882
    Wildepiet
    Jun 14, 2004
  2. Axel Straschil
    Replies:
    6
    Views:
    380
    Axel Straschil
    Apr 11, 2005
  3. Replies:
    7
    Views:
    678
    James Kanze
    Jul 3, 2007
  4. Daniel Pitts
    Replies:
    27
    Views:
    1,956
    Mike Schilling
    Feb 27, 2008
  5. Rouslan Korneychuk
    Replies:
    8
    Views:
    619
    Rouslan Korneychuk
    Feb 10, 2011
Loading...

Share This Page