1. 程式人生 > >C++繼承與建構函式、複製控制

C++繼承與建構函式、複製控制

        每個派生類物件由派生類中定義的(非static)成員加上一個或多個基類子物件構成,因此,當構造、複製、賦值和撤銷派生型別物件時,也會構造、複製、賦值和撤銷這些基類子物件。

建構函式和複製控制成員不能繼承,每個類定義自己的建構函式和複製控制成員。像任何類一樣,如果類不定義自己的預設建構函式和複製控制成員,就將使用合成版本。

1:建構函式和繼承

       派生類的建構函式受繼承關係的影響,每個派生類建構函式除了初始化自己的資料成員之外,還要初始化基類。

派生類的合成預設建構函式:除了初始化派生類的資料成員之外,它還初始化派生類物件的基類部分。基類部分由基類的預設建構函式初始化:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};
int main()
{
    son ss1;
    ss1.display();
}

執行”son ss1;”語句時,呼叫派生類son的合成的預設建構函式,該函式會首先呼叫基類的預設建構函式。上述程式碼的結果是:

father constructor
[son]publ_i is 1
[son]prot_i is 3

如果派生類自己定義了建構函式,則該建構函式會隱式呼叫基類的預設建構函式:

class son: public father
{
public:
    son(int a = 2):mypriv_i(a)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};

int main()
{
    son ss1;
    ss1.display();
}

執行”son ss1;”語句時,呼叫派生類son的預設建構函式,該函式首先呼叫基類的預設建構函式初始化基類部分,然後,使用初始化列表初始化son::mypriv_i,最後,在執行son建構函式的函式體。上述程式碼的結果是:

father constructor
son constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 2

派生類建構函式還可以明確的在初始化列表中呼叫基類的建構函式,來間接的初始化基類成員。注意,在派生類列表中,不能直接初始化基類成員:

class son: public father
{
public:
    son(int a = 2):mypriv_i(a),father(100)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1;
    ss1.display();
}

不管初始化列表中的順序如何,派生類建構函式,都是首先呼叫基類建構函式初始化基類成員,然後是根據派生類成員的宣告順序進行初始化。

         如果嘗試在初始化列表中直接初始化基類成員,會發生編譯錯誤:

    son(int a = 2):mypriv_i(a),publ_i(100)
    {
        cout << "son constructor" << endl;
    }

        報錯如下:

test2.cpp: In constructor ‘son::son(int)’:
test2.cpp:29:29: error: class ‘son’ does not have any field named ‘publ_i’
  son(int a = 2):mypriv_i(a),publ_i(100)
                             ^

派生類建構函式不能初始化基類的成員且不應該對基類成員賦值。如果那些成員為 public 或 protected,派生建構函式可以在建構函式函式體中給基類成員賦值,但是,這樣做會違反基類的介面。派生類應通過使用基類建構函式尊重基類的初始化意圖,而不是在派生類建構函式函式體中對這些成員賦值。

注意,一個類只能初始化自己的直接基類。

2:複製控制和繼承

派生類合成的複製控制函式,對物件的基類部分連同派生部分的成員一起進行復制、賦值或撤銷,自動呼叫基類的複製建構函式、賦值操作符或解構函式對基類部分進行復制、賦值或撤銷:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
    {
        cout << "father copy constructor" << endl;
    }
    
    father& operator=(const father& src)
    {
        publ_i = src.publ_i;
        priv_i = src.priv_i;
        prot_i = src.prot_i;
        
        cout << "father operator = " << endl;
        return *this;
    }
    ~father()
    {
        cout << "father destructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a = 2):mypriv_i(a)
    {
        cout << "son constructor" << endl;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1(3);
    son ss2(ss1);
    ss2.display();
    
    son ss3;
    ss3 = ss1;
    ss3.display();
}

“son ss2(ss1);”語句,將呼叫派生類son的合成的複製建構函式,該建構函式將會自動呼叫基類的複製建構函式;”ss3 = ss1;”語句,將呼叫派生類son的合成的賦值操作符函式,該函式會自動呼叫基類的賦值操作符函式;最後,程式退出之前,會將派生類son的三個物件進行析構,從而自動呼叫基類的解構函式;上述程式碼的結果如下:

father constructor
son constructor
father copy constructor
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator = 
[son]publ_i is 1
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor

如果派生類自己定義了複製建構函式或賦值操作符,則應該顯示的呼叫基類的複製建構函式或賦值操作符:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
    }
    father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i)
    {
        cout << "father copy constructor" << endl;
    }
    
    father& operator=(const father& src)
    {
        publ_i = src.publ_i;
        priv_i = src.priv_i;
        prot_i = src.prot_i;
        
        cout << "father operator = " << endl;
        return *this;
    }
    ~father()
    {
        cout << "father destructor" << endl;
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }
private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son(int a = 2):mypriv_i(a),father(100)
    {
        cout << "son constructor" << endl;
    }
    son(const son& src):mypriv_i(src.mypriv_i),father(src)
    {
        cout << "son copy constructor" << endl;
    }
    son& operator=(const son& src)
    {
        father::operator=(src);
        mypriv_i = src.mypriv_i;
        
        cout << "son operator = " << endl;
        return *this;
    }
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
        cout << "[son]mypriv_i is " << mypriv_i << endl;
    }
private:
    int mypriv_i;
};
int main()
{
    son ss1(3);
    son ss2(ss1);
    ss2.display();
    
    son ss3;
    ss3 = ss1;
    ss3.display();
}

在派生類son的複製建構函式中,初始化列表中顯示呼叫基類複製建構函式使用src初始化基類部分。這裡如果省略呼叫基類複製建構函式的呼叫的話,編譯器會自動呼叫基類的預設建構函式初始化基類部分,這就造成新構造的物件會有比較奇怪的配置,它的基類部分儲存預設值,而他的派生類部分是src的副本;

在派生類son的賦值操作符函式中,顯示呼叫了基類的賦值操作符函式。如果省略了這一呼叫,則賦值操作,僅僅使該物件的mypriv_i與src的mypriv_i相同;

上述程式碼的結果如下:

father constructor
son constructor
father copy constructor
son copy constructor
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father constructor
son constructor
father operator = 
son operator = 
[son]publ_i is 100
[son]prot_i is 3
[son]mypriv_i is 3
father destructor
father destructor
father destructor

如果定義了派生類的解構函式,則不同於複製建構函式和賦值操作符:編譯器會自動呼叫基類的解構函式,因此在派生類的解構函式中,只負責清除自己的成員:

    ~son()
    {
        cout << "son destructor" << endl;
    }

定義了son的解構函式之後,上述程式碼的結果是:

...
son destructor
father destructor
son destructor
father destructor
son destructor
father destructor

物件的撤銷順序與構造順序相反:首先執行派生解構函式,然後按繼承層次依次向上呼叫各基類解構函式。

另外,如果基類指標指向了一個派生類物件,則刪除該基類指標時,如果基類的解構函式不是虛擬函式的話,則只會呼叫基類的解構函式,而不會呼叫派生類解構函式。比如還是上面的father和son兩個類,執行下面的程式碼:

    father *fp = new son;
    delete fp;

結果就是:

father constructor
son constructor
father destructor

為了避免這種情況的發生,應該將基類的解構函式定義為虛擬函式,這樣,無論派生類顯式定義解構函式還是使用合成解構函式,派生類解構函式都是虛擬函式。從而可以得到正確的結果。

因此,即使解構函式沒有工作要做,繼承層次的根類也應該定義一個虛解構函式。

3:建構函式或解構函式中呼叫虛擬函式

在執行建構函式或解構函式的時候,物件都是不完整的。為了適應這種不完整,編譯器將物件的型別視為在構造或析構期間發生了變化。在基類建構函式或解構函式中,將派生類物件當作基類型別物件對待。

因此,如果是在建構函式或解構函式中呼叫虛擬函式,則執行的是為建構函式或解構函式自身型別定義的版本。如果在基類建構函式中呼叫虛擬函式,則構造派生類的時候,該虛擬函式實際上還是呼叫的基類的版本:

class father
{
public:
    int publ_i;
    father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) 
    {
        cout << "father constructor" << endl;
        display();
    }
    virtual void display()
    {
        cout << "[father]publ_i is " << publ_i << endl;
        cout << "[father]priv_i is " << priv_i << endl;
        cout << "[father]prot_i is " << prot_i << endl;
    }

private:
    int priv_i;
protected:
    int prot_i;
};

class son: public father
{
public:
    son()
    {
        cout << "son constructor" << endl;
    }
    
    void display()
    {
        cout << "[son]publ_i is " << publ_i << endl;
        cout << "[son]prot_i is " << prot_i << endl;
    }
};

int main()
{
    father *fp = new son;
}

在構造son物件時,需要呼叫father的建構函式,在father的建構函式中呼叫了虛擬函式display。此時,雖然構造的是派生類物件,但是這裡依然還是呼叫基類的display函式。因此,上述程式碼的結果為:

father constructor
[father]publ_i is 1
[father]priv_i is 2
[father]prot_i is 3
son constructor

無論由建構函式(或解構函式)直接呼叫虛擬函式,或者從建構函式(或解構函式)所呼叫的函式間接呼叫虛擬函式,都應用這種繫結。

要理解這種行為,考慮如果從基類建構函式(或解構函式)呼叫虛擬函式的派生類版本會怎麼樣。虛擬函式的派生類版本很可能會訪問派生類物件的成員,但是,物件的派生部分的成員不會在基類建構函式執行期間初始化,實際上,如果允許這樣的訪問,程式很可能會崩潰。