12

Please read the code to understand the situation.

#include <iostream>
using namespace std;
class one
{
protected:
    int x;
public:
    one(int a)
    {
        x=a;
        cout << "one cons called\n";
    }
    void display(void)
    {
        cout << "x = " << x << endl;
    }
    ~one()
    {
        cout << "one destroy\n";
    }
};
class two : virtual protected one
{
protected:
    int y;
public:
    two(int a,int b) : one(a),y(b)
    {
        cout << "two cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "y = " << y << endl;
    }
    ~two()
    {
        cout << "two destroy\n";
    }
};

class three : protected virtual one
{
protected:
    int z;
public:
    three(int a,int b) : one(a),z(b)
    {
        cout << "Three cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "z = " << z << endl;
    }
    ~three()
    {
        cout << "three destroy\n";
    }
};

class four : private two, private three
{
public:
    four(int a,int b,int c) :one(a), two(a,b),three(a,c)
    {
        cout << " four cons called\n";
    }
    void display(void)
    {
        one::display();
        cout << "y = " << y << endl;
        cout << "z = " << z << endl;
    }
    ~four()
    {
        cout << "four destroy\n";
    }
};
int main()
{
    four ob(1,2,3);
    ob.display();
    return 0;
}

If i replace the code

four(int a,int b,int c) :one(a), two(a,b),three(a,c)

with

four(int a,int b,int c) :two(a,b),three(a,c)

an error messege like : no matching function for call to 'one::one()' occurs in my codeblock ide.

As you can see this is a code based on diamond problem.Where class one is the grand_parent class . Class two and three serving as parent class and class four as child class. So i used the virtual keyword to avoid ambiguity. Everything I understand here unless 1 thing.I know that when a parent class has parameterized constructor we need to supply arguments to that constructor from the derived class. So then Why do need to supply argument to the constructor one where class four has only 2 parent class that is two and three . The code will give me compile time error if i don't call constructor one from the class four. Please explain me why we need to do so.

  • 1
    There's a few suspect things about the code and classes you show. Like for example why do you override the ***non***-virtual function `display` in all classes? And why do you have `private` inheritance? Private inheritance is usually recommended against in favor of composition. – Some programmer dude Jul 26 '19 at 07:03
  • 1
    @Someprogrammerdude .I wrote this code for learning purposes , didn't write this for real project –  Jul 26 '19 at 09:59
  • 1
    Possible duplicate: https://stackoverflow.com/questions/44324583/why-must-virtual-base-classes-be-constructed-by-the-most-derived-class – Caleth Jul 26 '19 at 10:29

3 Answers3

19

The virtual inheritance in your hierarchy disambiguates the existence of the base class one by making sure that only one single instance of one is stored in subclasses of two or three. Recall that when inheriting some class, a derived instance will always store a base instance somehere internally - so virtual inheritance ensures that the instances of one inside of two and three are somewhat "overridden" by any class further down the inheritance hierarchy.

Now the question is: who is responsible for initializing this one single one instance? Should it be two or three? Clearly not both of them, as there is only one instance. And here you are: it's always the most derived class that's responsible for the initialization of one - and that makes sense: the instance that embeds a base class copy must initialize it.

This is how the class hierarchy with embedded base class instances looks like without four and with four plus virtual inheritance:

              +----------+                           +----------+
              |   one    |                           |   one    |
              +----+-----+                           +----+-----+
                   |                                      |
                   |                                      |
         +-------+-----------+           virtual +--------+--------+ virtual
         |                   |                   |                 |
         |                   |                   |                 |
+--------+-------+   +-------+-------+      +----+----+       +----+----+
|      two       |   |      three    |      |  two    |       |  three  |
| +------------+ |   | +----------+  |      +----+----+       +----+----+
| |   one      | |   | |   one    |  |           |                 |
| +------------+ |   | +----------+  |           +--------+--------+
|  => must init! |   | => must init! |                    |
+----------------+   +---------------+            +-------+--------+
                                                  |     four       |
                                                  | +------------+ |
                                                  | |    one     | |
                                                  | +------------+ |
                                                  | => must init!  |
                                                  +----------------+

You can think of this mechanism this way: virtual inheritance gives the base class instance virtual-ness, and that includes constructing the instance - this responsability is passed down the hierarchy.

lubgr
  • 37,368
  • 3
  • 66
  • 117
6

Say you have the following diamond:

     Base
    /    \
 Left    Right
    \    /
     Down

The Base class can be very simple, it has a single int member that is initialized by the constructor:

struct Base
{
    Base(int x) 
        : x(x)
    {}
    virtual ~Base() = default;
    int x;
};

Since Left inherits from Base, its constructor can pass arguments to the Base constructor. Here, if you construct a Left object, its x member will be 1:

struct Left : virtual Base
{
    Left() : Base(1)
    {}
};

The other class, Right, also inherits from Base. This means that its constructor can also pass arguments to the Base constructor. Here, its x member will be 2:

struct Right : virtual Base
{
    Right() : Base(2)
    {}
};

Now comes the fun part: What happens if you inherit from both Left and Right?

// This does not compile.
struct Down : Left, Right
{
    Down() : Left(), Right()
    {}
};

Both Left and Right call the Base constructor, but they use different arguments. Should the compiler now use the Base(1) part from Left or should it use the Base(2) part from Right? The answer is simple: It uses neither! The compiler leaves the choice to you and allows you to specify which constructor should be used:

// Hooray, this version compiles.
struct Down : Left, Right
{
    Down() : Base(42), Left(), Right()
    {}
};
pschill
  • 5,055
  • 1
  • 21
  • 42
2

The Diamond problem happens when two super classes of a class have a common base class. The solution for this problem is “Virtual” keyword. In general case it is not allowed to call the grandparent’s constructor directly, it has to be called through parent class. It is allowed only when we use “Virtual” keyword. SO, when we use ‘virtual’ keyword, the default constructor of grandparent class is called by default even if the parent classes explicitly call parameterized constructor.