Pull to refresh

Diamond inheritance problem is not a problem, that's a tricky feature

Reading time 7 min
Views 4.1K

General


Before discussing the topic I’d like to start with a general suggestion not to use multiple inheritance and especially diamond unless you are strongly forced to. You may use e.g. composition or aggregation instead.


Well, “Diamond inheritance problem” is some kind of steady expression which formed many years ago. You can easily find a lot of articles suggesting usage of “virtual public” to avoid the ambiguity and so on. For instance, https://en.wikipedia.org/wiki/Multiple_inheritance


image


 That is not wrong as for the problem stated but anyway it is quite one-side statement.
 Below you can find:


  • difference in memory allocation and initialization order between public and public virtual inheritance (examples 1, 2),
  • examples of practical usage of both public and public virtual inheritance (examples 3, 4).

Example 1. Non-virtual inheritance


#include <iostream>
#include <stdio.h>

using namespace std;

class A{
    public:
    A(void) : mDataA(0)
    {
        cout << "A(void)" << endl; 
        printf("&mDataA = %8.8lX\n", &mDataA);
    }
    virtual ~A() 
    {
        cout << "virtual ~A()" << endl;
    }

    int mDataA;
};

class B : public A{
    public:
    B(void) : A(), mDataB(0)
    {
        cout << "B(void)" << endl;
        printf("&mDataB = %8.8lX mDataA = %8.8lX\n", &mDataB, &mDataA);
    }
    virtual ~B() 
    {
        cout << "virtual ~B()" << endl;
    }
    int mDataB;
};

class C : public A{
    public:
    C(void) : A(), mDataC(0)
    {
        cout << "C(void)" << endl; 
        printf("&mDataC = %8.8lX mDataA = %8.8lX\n", &mDataC, &mDataA);
    }
    virtual ~C() 
    {
        cout << "virtual ~C()" << endl;
    }
    int mDataC;
};

class D: public B, public C{
    public:
    D(void) : B(), C(), mDataD(0)
    {
        cout << "D(void)" << endl; 
        printf("&mDataD = %8.8lX\n", &mDataD);
    }
    virtual ~D() 
    {
        cout << "virtual ~D()" << endl;
    }
    int mDataD;
};

int main() {
    D d;
    cout << "\nsizeof(D) = " << sizeof(D) << "\n\n";
}

/*output
A(void)
&mDataA = 7FFF4F247788
B(void)
&mDataB = 7FFF4F24778C mDataA = 7FFF4F247788
A(void)
&mDataA = 7FFF4F247798
C(void)
&mDataC = 7FFF4F24779C mDataA = 7FFF4F247798
D(void)
&mDataD = 7FFF4F2477A0

sizeof(D) = 40

virtual ~D()
virtual ~C()
virtual ~A()
virtual ~B()
virtual ~A()
*/

As you can see the data stored in memory the following way:


  • Class A (inherited in B),
  • Class B,
  • Class A (inherited in C),
  • Class C,
  • Class D.

Example 2. Virtual inheritance


#include <iostream>
#include <stdio.h>

using namespace std;

class A{
    public:
    A(void) : mDataA(0)
    {
        cout << "A(void)" << endl; 
        printf("&mDataA = %8.8lX\n", &mDataA);
    }
    virtual ~A() 
    {
        cout << "virtual ~A()" << endl;
    }

    int mDataA;
};

class B : public virtual A{
    public:
    B(void) : A(), mDataB(0)
    {
        cout << "B(void)" << endl;
        printf("&mDataB = %8.8lX mDataA = %8.8lX\n", &mDataB, &mDataA);
    }
    virtual ~B() 
    {
        cout << "virtual ~B()" << endl;
    }
    int mDataB;
};

class C : public virtual A{
    public:
    C(void) : A(), mDataC(0)
    {
        cout << "C(void)" << endl; 
        printf("&mDataC = %8.8lX mDataA = %8.8lX\n", &mDataC, &mDataA);
    }
    virtual ~C() 
    {
        cout << "virtual ~C()" << endl;
    }
    int mDataC;
};

class D: public virtual B, public virtual C{
    public:
    D(void) : B(), C(), mDataD(0)
    {
        cout << "D(void)" << endl; 
        printf("&mDataD = %8.8lX\n", &mDataD);
    }
    virtual ~D() 
    {
        cout << "virtual ~D()" << endl;
    }
    int mDataD;
};

int main() {
    D d;
    cout << "\nsizeof(D) = " << sizeof(D) << "\n\n";

}

/*output
A(void)
&mDataA = 7FFD9E080018
B(void)
&mDataB = 7FFD9E080008 mDataA = 7FFD9E080018
C(void)
&mDataC = 7FFD9E080028 mDataA = 7FFD9E080018
D(void)
&mDataD = 7FFD9E07FFF8

sizeof(D) = 64

virtual ~D()
virtual ~C()
virtual ~B()
virtual ~A()
*/

 As you can see now A is not doubled and order of classes’ data differs:


  • Class D,
  • Class B,
  • Class A,
  • Class C.
     Please also note that sizeof(D) is bigger due to adding special offsets/pointers in order each class to know where its inherited base class is allocated in memory.

 For more details about memory allocation for multiple inheritance please see https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable-ctor-vtt


Example 3. Pseudo-practical usage of non-virtual inheritance


#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <map>
#include <iomanip>

using namespace std;

//               Price
//                 ^
//                 |
//          -------------
//         |             \
//   WholesalePrice   RetailPrice
//        ^              ^
//        |              /
//        ---------------
//                |
//            GrossMargin

class Price{
public:

   Price(int Price) : 
         mPrice(Price)
   {
      cout << "Price(" << Price << ") this = " << this << endl;
   }

   virtual ~Price()
   {
       cout << "~Price() this = " << this << endl;
   }

   inline int getPrice() const
   {
      return mPrice;    
   }

   int mPrice;
};

class WholesalePrice : public Price
{
public:
   WholesalePrice(int WholesalePrice) : 
                  Price(WholesalePrice)
   {
        cout << "WholesalePrice(" << WholesalePrice << ") this = " << this << endl;
        cout << "mPrice =  " << mPrice << ", getPrice() = " << getPrice() << endl;
   }

   virtual ~WholesalePrice()
   {
       cout << "~WholesalePrice() this = " << this << endl;
   }
};

class RetailPrice : public Price{
public:
   RetailPrice(int RetailPrice) : 
               Price(RetailPrice)
   {
        cout << "RetailPrice(" << RetailPrice << ") this = " << this << endl;
        cout << "mPrice =  " << mPrice << ", getPrice() = " << getPrice() << endl;
   }

   virtual ~RetailPrice()
   {
       cout << "~RetailPrice() this = " << this << endl;
   }
};

template  <class TRevenue, class TCost>
class GrossMargin : public TRevenue, public TCost{
public:    

    GrossMargin(int Revenue, int Cost) : 
                TRevenue(Revenue), 
                TCost(Cost), 
                mGrossMargin((TRevenue::getPrice() - TCost::getPrice()) * 100 / TRevenue::getPrice())
    {
       cout <<  "GrossMargin(" << TRevenue::getPrice() << ", " << TCost::getPrice() << ") mGrossMargin = " << mGrossMargin << " this = " << this << endl;
    }

    virtual ~GrossMargin()
    {
         cout << "~GrossMargin()  this = " << this << endl;
    }

    inline int getMargin(void) const
    {
        return mGrossMargin;
    }

    int mGrossMargin;
};

void test(void)
{
    cout << "Enter test\n"; 
    GrossMargin<RetailPrice, WholesalePrice> gm(160, 120);
    cout << "\n\nExit test\n"; 
}

int main() {
    test();
}

/*Output
Enter test
Price(160) this = 0x7ffcec7ff4a0
RetailPrice(160) this = 0x7ffcec7ff4a0
mPrice =  160, getPrice() = 160
Price(120) this = 0x7ffcec7ff4b0
WholesalePrice(120) this = 0x7ffcec7ff4b0
mPrice =  120, getPrice() = 120
GrossMargin(160, 120) mGrossMargin = 25 this = 0x7ffcec7ff4a0

Exit test
~GrossMargin()  this = 0x7ffcec7ff4a0
~WholesalePrice() this = 0x7ffcec7ff4b0
~Price() this = 0x7ffcec7ff4b0
~RetailPrice() this = 0x7ffcec7ff4a0
~Price() this = 0x7ffcec7ff4a0
*/

Example 4. Pseudo-practical usage of virtual inheritance


#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <map>
#include <iomanip>

using namespace std;

//               Price
//                 ^
//                 |
//          -------------
//         |             \
//   WholesalePrice   RetailPrice
//        ^              ^
//        |              /
//        ---------------
//                |
//            GrossMargin

class Price{
public:

   Price(void) : 
         mPrice(0)
   {
      cout << "Price() this = " << this << endl;
   }

   virtual ~Price()
   {
       cout << "~Price() this = " << this << endl;
   }

   virtual inline int getPrice() const
   {
      return mPrice;    
   }

   inline void setPrice(int Price)
   {
      mPrice = Price;
   }

private:
   int mPrice;
};

class WholesalePrice : public virtual Price
{
public:
   WholesalePrice(void) : 
                  Price(),
                  mWholesaleExtraCharge(0)
   {
        cout << "WholesalePrice() this = " << this << endl;
   }

   virtual ~WholesalePrice()
   {
       cout << "~WholesalePrice() this = " << this << endl;
   }

    virtual inline int getPrice() const
   {
      return Price::getPrice()*(100+mWholesaleExtraCharge)/100;  
   }

   inline void setWholesaleExtraCharge(int ExtraCharge)
   {
      mWholesaleExtraCharge = ExtraCharge;
   }

private:   
   int mWholesaleExtraCharge;
};

class RetailPrice : public virtual Price{
public:
   RetailPrice(void) : 
               mRetailExtraCharge(0)
   {
        cout << "RetailPrice() this = " << this << endl;
   }

   virtual ~RetailPrice()
   {
       cout << "~RetailPrice() this = " << this << endl;
   }

   virtual inline int getPrice() const
   {
      return Price::getPrice()*(100+mRetailExtraCharge)/100;  
   }

   inline void setRetailExtraCharge(int ExtraCharge)
   {
      mRetailExtraCharge = ExtraCharge;
   }

private:   
   int mRetailExtraCharge;
};

template  <class TRevenue, class TCost>
class GrossMargin : public TRevenue, public TCost{
public:    

    GrossMargin(void) : 
                TRevenue(), 
                TCost() 
    {
       cout <<  "GrossMargin() this = " << this << endl;
    }

    virtual ~GrossMargin()
    {
         cout << "~GrossMargin()  this = " << this << endl;
    }

    inline int getMargin(void) const
    {
       return (TRevenue::getPrice() - TCost::getPrice()) * 100 / TRevenue::getPrice();
    }

private:
   virtual inline int getPrice() const final
   {
      return 0;  
   }
};

void test(void)
{
    cout << "Enter test\n"; 
    GrossMargin<RetailPrice, WholesalePrice> gm;
    gm.setPrice(100);//basic price
    gm.setWholesaleExtraCharge(20);//local wholesale price
    gm.setRetailExtraCharge(60);//local retail price
    cout << "\nGross margin = " << gm.getMargin() << endl;
    cout << "\nExit test\n\n"; 
}

int main() {
    test();
}

/*Output
Enter test
Price() this = 0x7ffc6d9a2e80
RetailPrice() this = 0x7ffc6d9a2e60
WholesalePrice() this = 0x7ffc6d9a2e70
GrossMargin() this = 0x7ffc6d9a2e60

Gross margin = 25

Exit test

~GrossMargin()  this = 0x7ffc6d9a2e60
~WholesalePrice() this = 0x7ffc6d9a2e70
~RetailPrice() this = 0x7ffc6d9a2e60
~Price() this = 0x7ffc6d9a2e80
*/

Conclusion


As you see both “public” and “public virtual” inheritance might be used for the specific purpose. We may consciously use different data/behavior of the base class as well as use common data/behavior for all the inherited classes.


However, as I state in the begging, the same functionality may be implemented using composition or aggregation.


For instance, for complex cases when you need to implement callbacks reloading virtual methods of several base classes and to process them in one class, I’d suggest the pattern observer as a robust scalable solution.


The only really valuable outcome from playing with diamond inheritance examples is to learn by heart the inheritance mechanism and allocation in memory to avoid magic bugs in the code.

Tags:
Hubs:
+4
Comments 0
Comments Leave a comment

Articles