簡體   English   中英

在c ++的鑽石問題中,為什么我們需要從子類中調用grand_parent構造函數?

[英]In a diamond problem in c++ , Why do we need to call grand_parent constructor from child class?

請閱讀代碼以了解情況。

#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;
}

如果我替換代碼

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

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

一個錯誤消息:在我的codeblock ide中沒有用於調用'one :: one()的匹配函數。

正如您所看到的,這是一個基於鑽石問題的代碼。第一類是grand_parent類。 第二和第三類作為父類,第四類作為子類。 所以我使用虛擬關鍵字來避免歧義。 除非有一件事我在這里理解的一切。我知道當父類具有參數化構造函數時,我們需要從派生類提供該構造函數的參數。 那么為什么需要為構造函數提供參數,其中類4只有2個父類,即2和3。 如果我不從第四類調用構造函數1,代碼將給出編譯時錯誤。 請解釋我為什么需要這樣做。

virtual層次結構中的繼承歧義消除的基類的存在, one通過確保只有一個單一實例one存儲在子類twothree 回想一下,繼承一些類時,派生實例將始終存儲基本實例somehere內部-所以virtual繼承確保的情況下, one內部的twothree是有點“重寫”的任何類進一步下跌的繼承層次。

現在的問題是:誰是負責初始化這一個單一的one實例? 它應該是two還是three 顯然不是兩者都有,因為只有一個例子。 在這里你是:它總是最派生的類負責初始化one - 這是有道理的:嵌入基類副本的實例必須初始化它。

這就是具有嵌入式基類實例的類層次結構在沒有fourfour加上virtual繼承的情況下的樣子:

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

您可以通過這種方式考慮這種機制: virtual繼承為基類實例提供virtual ,包括構造實例 - 此責任性在層次結構中傳遞。

說你有以下鑽石:

     Base
    /    \
 Left    Right
    \    /
     Down

Base類可以非常簡單,它有一個由構造函數初始化的int成員:

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

由於Left繼承自Base ,因此其構造函數可以將參數傳遞給Base構造函數。 在這里,如果構造一個Left對象,其x成員將為1

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

另一個類Right也繼承自Base 這意味着它的構造函數也可以將參數傳遞給Base構造函數。 這里,它的x成員將是2

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

現在來到有趣的部分:如果你從LeftRight繼承會發生什么?

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

LeftRight調用Base構造函數,但它們使用不同的參數。 編譯器現在應該使用LeftBase(1)部分還是應該使用RightBase(2)部分? 答案很簡單:它既不使用! 編譯器將選擇留給您,並允許您指定應使用哪個構造函數:

// Hooray, this version compiles.
struct Down : Left, Right
{
    Down() : Base(42), Left(), Right()
    {}
};

當一個類的兩個超類具有公共基類時,就會發生Diamond問題。 此問題的解決方案是“虛擬”關鍵字。 一般情況下,不允許直接調用祖父母的構造函數,必須通過父類調用它。 只有在我們使用“Virtual”關鍵字時才允許它。 因此,當我們使用'virtual'關鍵字時,即使父類顯式調用參數化構造函數,默認情況下也會調用祖父類的默認構造函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM