3

我有以下C++代碼(VS2013如果該事項):C++虛繼承和構造

#include <iostream> 
using namespace std; 

struct A{ 
    A() { cout << "A()" << endl; } 
    A(int i) { cout << "A(" << i << ")" << endl; } 
}; 

struct B : /*virtual*/ public A{ 
    B(int i) : A(i){ cout << "B(" << i << ")" << endl; } 
}; 

struct C : /*virtual*/ public A{ 
    C(int i) : A(i){ cout << "C(" << i << ")" << endl; } 
}; 

struct D : /*virtual*/ public B, /*virtual*/ public C{ 
    D() : B(2), C(3){ cout << "D()" << endl; } 
}; 

void main() { 
    D d; 
    system("pause"); 
} 

的代碼實現菱形繼承,用4個遺產總數: B:A,C: A,d:B,d:C

隨着無這些繼承設置爲virtual,我得到以下輸出:

A(2)B(2)A(3)C(3) D()

這很有道理。 D()首先調用首先調用A(int x)的B(int x),然後調用首先調用A(int x)的C(int x),最後調用D()本身。

但是就虛擬繼承而言,我有4個繼承,這意味着我總共有16個虛擬\非虛擬繼承的組合。

在不同的選項中亂作一團會產生一些非常意想不到的結果,而我對虛擬繼承的瞭解越多,我似乎越感到困惑。

下面是一些例子:

  1. 當設置僅B:A至virtual我得到:

A()B(2)A(3)C(3)d ()

這是有道理的 - B實際上繼承了A,因此,除非特別聲明,否則B(int x)調用A的默認構造函數。

  • 當是僅含有C設定:A到virtual我得到:
  • A()A(2)B(2)C(3)d()

    爲什麼A的構造函數都在B和C之前?這種行爲對我來說毫無意義。

  • 當只有d設定:C到virtual我得到:
  • A(3)C(3)(2)B(2)d( )

    爲什麼C的構造函數會先於B的?

    我可以繼續下去。 其中一些組合會導致非常意想不到的結果(至少對我而言)。

    我知道虛擬繼承的基礎知識,並通過簡單的例子來理解它們,並且我已經看到很多關於它的問題。但是其中一些行爲仍然困擾着我。

    這些多重虛擬繼承是否有任何特定的規則集?

    編輯: 我被重定向到這個問題: Mixing virtual and non-virtual inheritance of a base class Whlie,幫助了很多,我作爲其中A的構造函數在任何特定情況被稱爲仍然感到困惑。 我仍然需要一些幫助。

    回答

    2

    有兩個大按鍵的位置:

    1. 虛基類是直接由最派生類的構造函數初始化,而不是通過其他基類是間接的。

    2. 虛擬基類在任何非虛擬基類之前被初始化。

    讓我們來看看你的例子。

    當僅有B設定:A到虛擬I得到:

    A()B(2)A(3)C(3)d()

    這使得某種意義上 - B繼承一個虛擬的,因此,除非另有特別說明,B(int x)調用A的默認構造函數。

    B根本不叫A的構造函數。 D直接針對A的虛擬實例調用A。但由於D的構造函數沒有指定如何初始化A,所以你得到了A的默認構造函數。

    B(int i) : A(i)A被不被忽略,因爲A是一個虛擬的基礎,但由於B不是最派生類,因此不會去構建它的A

    當只有C程序中設置:A到虛擬I得到:

    A()A(2)B(2)C(3)d()

    爲什麼A的構造函數都先於B的和C的?這種行爲對我來說毫無意義。

    這裏C::A是唯一的虛擬基類,所以它首先被初始化。同樣,由於D的構造函數沒有爲A指定初始值設定項,該虛擬子對象使用默認的構造函數。

    然後非虛擬基類按順序進行。 BC之前,但B首先需要初始化其非虛擬A(與2)。

    當只有d設置:C到虛擬I得到:

    A(3)C(3)A(2)B(2)d()

    爲什麼C'S構造先B的?

    唯一的虛擬基類是C,因此它在B之前構建。但是這次C的構造函數必須首先初始化它的非虛擬基子對象C::A。然後B,它必須首先初始化其非虛擬子對象B::A

    最後,出現了「正常」模式,這使得兩個A繼承是虛擬的,導致只有一個A子對象,這是虛擬繼承的整個點。除非您希望D以更復雜的方式作爲基類重用,否則將BC虛擬爲毫無意義。

    #include <iostream> 
    using namespace std; 
    
    struct A{ 
        A() { cout << "A()" << endl; } 
        A(int i) { cout << "A(" << i << ")" << endl; } 
    }; 
    
    struct B : virtual public A{ 
        B(int i) : A(i){ cout << "B(" << i << ")" << endl; } 
    }; 
    
    struct C : virtual public A{ 
        C(int i) : A(i){ cout << "C(" << i << ")" << endl; } 
    }; 
    
    struct D : public B, public C{ 
        D() : A(1), B(2), C(3){ cout << "D()" << endl; } 
    }; 
    
    void main() { 
        D d; 
        system("pause"); 
    } 
    

    輸出:

    A(1)B(2)C(3)d()

    注我已經添加的初始化A(1)D,就表明虛擬子對象不一定需要使用默認的構造函數。您可以在您的任何示例中完成這項工作,其中至少有一個A繼承是虛擬的。