在c ++中的菱形问题中,为什么我们需要从子类中调用grand_parent构造函数?

11 c++ inheritance constructor diamond-problem

请阅读代码以了解情况。

#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;
}
Run Code Online (Sandbox Code Playgroud)

如果我替换代码

four(int a,int b,int c) :one(a), two(a,b),three(a,c)
Run Code Online (Sandbox Code Playgroud)

four(int a,int b,int c) :two(a,b),three(a,c)
Run Code Online (Sandbox Code Playgroud)

一个错误信息,如:我的代码块ide中没有匹配的函数调用“ one :: one()”。

如您所见,这是一个基于钻石问题的代码。第一个类是grand_parent类。第二和第三班作为父母班,第四班作为孩子班。因此,我使用了virtual关键字来避免歧义。除非有1件事,否则我在这里所了解的一切。我知道当父类具有参数化构造函数时,我们需要从派生类向该构造函数提供参数。因此,为什么需要向构造函数one提供参数,其中第四类只有两个父类,即第二和第三类。如果我不从类4调用构造函数一,代码将给我编译时错误。请解释一下为什么我们需要这样做。

lub*_*bgr 17

通过确保在或的子类中仅存储一个实例,virtual层次结构中的继承可消除基类的存在。回想一下,继承一些类时,派生实例将始终存储基本实例内部somehere -这样的继承确保的情况下,内部和有点“重写”的任何类进一步下跌的继承层次。oneonetwothreevirtualonetwothree

现在的问题是:谁负责初始化一个one实例?应该是two还是three?显然不是两者都存在,因为只有一个实例。您在这里:它始终是负责- 初始化的最派生类,one并且这很有意义:嵌入基类副本的实例必须对其进行初始化。

这是嵌入基类实例的外观的类层次结构怎么样,而不fourfourvirtual继承:

              +----------+                           +----------+
              |   one    |                           |   one    |
              +----+-----+                           +----+-----+
                   |                                      |
                   |                                      |
         +-------+-----------+           virtual +--------+--------+ virtual
         |                   |                   |                 |
         |                   |                   |                 |
+--------+-------+   +-------+-------+      +----+----+       +----+----+
|      two       |   |      three    |      |  two    |       |  three  |
| +------------+ |   | +----------+  |      +----+----+       +----+----+
| |   one      | |   | |   one    |  |           |                 |
| +------------+ |   | +----------+  |           +--------+--------+
|  => must init! |   | => must init! |                    |
+----------------+   +---------------+            +-------+--------+
                                                  |     four       |
                                                  | +------------+ |
                                                  | |    one     | |
                                                  | +------------+ |
                                                  | => must init!  |
                                                  +----------------+
Run Code Online (Sandbox Code Playgroud)

您可以这样想这种机制:virtual继承赋予基类实例virtual-ness,并且包括构造实例的责任-这种责任向下传递到层次结构中。

  • 您是手动创建还是使用工具创建该图? (2认同)
  • @ 0x499602D2首先草拟[here](http://asciiflow.com/),然后在vim中进行一些可视块编辑。 (2认同)

psc*_*ill 5

假设您有以下钻石:

     Base
    /    \
 Left    Right
    \    /
     Down
Run Code Online (Sandbox Code Playgroud)

Base类可以很简单,它有一个int由构造函数初始化成员:

struct Base
{
    Base(int x) 
        : x(x)
    {}
    virtual ~Base() = default;
    int x;
};
Run Code Online (Sandbox Code Playgroud)

由于Left继承自Base,其构造函数可以将参数传递给Base构造函数。在这里,如果你构造一个Left对象,它的x成员将是1

struct Left : virtual Base
{
    Left() : Base(1)
    {}
};
Run Code Online (Sandbox Code Playgroud)

另一个类Right也继承自Base。这意味着它的构造函数也可以将参数传递给Base构造函数。在这里,它的x成员将是2

struct Right : virtual Base
{
    Right() : Base(2)
    {}
};
Run Code Online (Sandbox Code Playgroud)

现在是有趣的部分:如果您同时继承Left和,会发生什么Right

// This does not compile.
struct Down : Left, Right
{
    Down() : Left(), Right()
    {}
};
Run Code Online (Sandbox Code Playgroud)

双方LeftRight调用Base构造函数,但它们使用不同的参数。编译器现在应该使用Base(1)from的部分Left还是应该使用Base(2)from的部分Right?答案很简单:它既不使用!编译器将选择权留给您,并允许您指定应使用哪个构造函数:

// Hooray, this version compiles.
struct Down : Left, Right
{
    Down() : Base(42), Left(), Right()
    {}
};
Run Code Online (Sandbox Code Playgroud)