C++中虛繼承派生類構造函數的正確寫法


最近工作中某個軟件功能出現了退化,追查下來發現是一個類的成員變量沒有被正確的初始化。這個問題與C++存在虛繼承的情況下派生類構造函數的寫法有關。在此說明一下錯誤發生的原因,希望對更多的人有幫助。

我們代碼中存在虛繼承的類的繼承結構與下圖類似,並不是教科書中經典的菱形結構。從 Intermediate1 和 Intermediate3 到Base2 的繼承是虛繼承。Base1 和 Base2 包含一些成員變量,並提供了相應的構造函數接受指定的初始化值。Base2 還有一個缺省構造函數,把其成員變量都初始化為0。Intermediate1,2,3 也都提供了一個構造函數接受指定的初始化值,並在在初始化列表里調用Base1和Base2的構造函數完成初始化。

image

一位同事在做重構時,不小心把Final的代碼改成了:

class Final : public Intermediate2, public Intermediate3 {
public:
    Final (int a, int b, int c)
        : Intermediate2(a, b, c),
          Intermediate3(b, c)
    {

    }

};
class Intermediate1 : public Base1, virtual public Base2 {
public:
    Intermediate1(int a, int b, int c)
        : Base1(a), 
          Base2(b, c)
    {

    }
};

class Intermediate2 : public Intermediate1 {
public:
    Intermediate2(int a, int b, int c)
        : Intermediate1(a, b, c),
          Base2(b, c)
    {

    }
};

class Intermediate3 : virtual public Base2 {
public:
    Intermediate3(int b, int c)
        : Base2(b, c)
    {

    }
};

看上去,Final的構造函數將調用Intermediate2 和 Intermediate3的構造函數分別將m_a, m_b 和 m_c初始化成指定的值。可是,運行時發現m_b和m_c的值是0!明顯,這是調用了Base2的缺省構造函數。

原來,C++的規則是:如果在繼承鏈上存在虛繼承的基類,則最底層的子類要負責完成該虛基類部分成員的構造。我們可以顯式調用虛基類的構造函數完成初始化。如果不顯式調用虛基類的構造函數,則編譯器會調用虛基類的缺省構造函數。如果不顯式調用虛基類的構造函數,而虛基類沒有定義缺省構造函數,則會出現編譯錯誤。這條規則的原因是:如果不這樣做,則虛基類部分會在存在的多個繼承鏈條上被多次初始化。

很多時候,對於繼承鏈上的中間類,我們也會在其構造函數中顯式調用虛基類的構造函數,因為一旦有人要創建這些中間類的對象,我們也要保證它們得到正確的初始化。

所以,如果我們要把m_b和m_c初始化成指定的值,Final的構造函數的正確寫法應該是這樣:

    Final (int a, int b, int c)
        : Base2(b, c),
          Intermediate2(a, b, c),
          Intermediate3(b, c)
    {

    }

完整的測試程序如下所示,有興趣的同學可以自行編譯運行一下。也可以在調試器中單步運行Final的構造函數,看看前后兩種寫法分別是調用了Base2的哪個構造函數。

#include "stdafx.h"
#include <iostream>

using namespace std;

class Base1 {
public:
    Base1(int a): m_a(a) {}

protected:
    int m_a;
};

class Base2 {
public:
    Base2(int b, int c): m_b(b), m_c(c) {}
    Base2() : m_b(0), m_c(0) {}

protected:
    int m_b;
    int m_c;
};

class Intermediate1 : public Base1, virtual public Base2 {
public:
    Intermediate1(int a, int b, int c)
        : Base1(a), 
          Base2(b, c)
    {

    }
};

class Intermediate2 : public Intermediate1 {
public:
    Intermediate2(int a, int b, int c)
        : Intermediate1(a, b, c),
          Base2(b, c)
    {

    }
};

class Intermediate3 : virtual public Base2 {
public:
    Intermediate3(int b, int c)
        : Base2(b, c)
    {

    }
};

class Final : public Intermediate2, public Intermediate3 {
public:
    Final (int a, int b, int c)
        : Base2(b, c),
          Intermediate2(a, b, c),
          Intermediate3(b, c)
    {

    }

    void Print() {
        cout<<m_a<<", "<<m_b<<", "<<m_c<<endl;
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    Final finalObj(1, 2, 3);
    finalObj.Print();

	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM