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
無論由建構函式(或解構函式)直接呼叫虛擬函式,或者從建構函式(或解構函式)所呼叫的函式間接呼叫虛擬函式,都應用這種繫結。
要理解這種行為,考慮如果從基類建構函式(或解構函式)呼叫虛擬函式的派生類版本會怎麼樣。虛擬函式的派生類版本很可能會訪問派生類物件的成員,但是,物件的派生部分的成員不會在基類建構函式執行期間初始化,實際上,如果允許這樣的訪問,程式很可能會崩潰。