15-7 派生類拷貝控制
15.7.1 虛解構函式
在基類中將解構函式定義為虛擬函式,用動態繫結機制來保證執行正確的解構函式版本
class Base{ public: virtual ~Quote() = default; //動態繫結解構函式 }; //Derived是Base的派生類 Base *itemP = new Base; //靜態型別與動態型別一致 delete itemP; //呼叫Base的解構函式 itemP = new Derived; //靜態型別與動態型別不一致 delete itemP; //呼叫Derived的解構函式
一個基類總是需要一個解構函式,且必須為虛擬函式
15.7.2 合成拷貝控制與繼承
本節不探討移動操作,只探討合成的預設建構函式以及合成的預設解構函式
派生類合成的預設建構函式
- 依次初始化派生類本身的成員
- 呼叫直接基類的合成的預設建構函式
如:Quote是基類,Disc_quote是Quote的派生類,Bulk_quote是Disc_quote的派生類
-
合成的Bulk_quote預設建構函式執行Disc_quote 的預設建構函式,後者又執行Quote 的預設建構函式。
-
Quote 的預設建構函式將bookNo成員預設初始化為空字串,同時使用類內初始值將price初始化為0。
-
ouote的建構函式完成後,繼續執行Disc_quote 的建構函式,它使用類內初始值初
始化 qty和 discount。
Disc_quote 的建構函式完成後,繼續執行Bulk_quote的建構函式,初始化Bulk_quote類的成員
如果基類構造了接受引數的建構函式而沒有定義預設建構函式,派生類無法合成預設的建構函式
class Base{
public :
//Base沒有預設建構函式
Base(int i);
};
class D : public Base{
public :
//派生類D無法合成預設的建構函式
};
無法建立D物件,因為它無法初始化
D d; //報錯:無法引用“D”的預設建構函式--它是已刪除的函式
派生類合成的預設解構函式
- 銷燬派生類自己的成員
- 銷燬派生類的直接基類
同樣:如果基類中的解構函式無法訪問或被定義為刪除的,那麼派生類中的解構函式也是刪除的,因為派生類無法析構它的基類部分
同時,派生類的合成的預設建構函式和拷貝函式也是刪除的,因為派生類無法析構它的基類部分
class Base{
public :
private:
//基類的合成的預設解構函式是刪除的
~Base();
};
class D : public Base{
public :
//派生類D的合成的預設建構函式、解構函式都是刪除的
};
總結
派生類的預設建構函式、拷貝建構函式、拷貝賦值運算子、解構函式都會呼叫基類的對應函式
如果基類的對應函式是無法訪問或者刪除的,那麼在派生類中也是刪除的
15.7.3 派生類的拷貝控制成員
直接用程式碼來說明問題
派生類的拷貝建構函式
class Base {/*...*/};
class D: public Base {
public :
//預設情況下,基類的預設建構函式初始化物件的基類部分
//想要使用拷貝或移動建構函式,我們必須在建構函式的初始值列表中
//顯式地呼叫該建構函式
D(const D& d): Base(d) //拷貝基類成員
/* D的成員的初始值 */{/**/}
D(D&& d): Base(std::move(d)) //移動基類成員
/* D的成員的初始值 */{/**/}
};
如果沒有提供基類的初始值
//D的這個拷貝建構函式很可能是不正確的
//基類部分被預設初始化,而非拷貝
D(const D& d): /* 成員初始值,沒有提供基類初始值*/
{/* ...*/}
派生類賦值運算子
顯示為基類部分賦值
//Base::operator=(const Base&)不會被自動執行
D& D::operator=(const D &rhs){
Base::operator=(rhs); //為基類部分賦值
//按照過去的方式為派生類成員賦值
//酌情處理自賦值及釋放已有資源等情況
return *this;
}
派生類的解構函式
與拷貝建構函式和賦值運算子不同,派生類的解構函式只負責析構自身的成員
class D:public Base{
public :
//Base::~Base被自動呼叫執行
~D() {/* 該處由使用者定義清除派生類成員的操作 */}
};
物件銷燬的順序正好與其建立的順序相反:派生類解構函式首先執行,然後是基類的解構函式,以此類推,沿著繼承體系的反方向直至最後。
15.7.4 繼承的預設建構函式
派生類“繼承”基類建構函式的方式是提供一條註明了(直接)基類名的using
宣告語句。
舉個例子,我們可以重新定義Bulk_quote類(參見15.4節,第541頁),令其繼承Disc_quote類的建構函式:
通常情況下,using宣告語句只是令某個名字在當前作用域內可見。而當作用於建構函式時,using宣告語句將令編譯器產生程式碼。
對於基類的每個建構函式,編譯器都生成一個與之對應的派生類建構函式。換句話說,對於基類的每個建構函式,編譯器都在派生類中生成一個形參列表完全相同的建構函式。
在我們的 Bulk_quote類中,繼承的建構函式等價於:
注意點
- 基類建構函式的預設實參不會被繼承
被繼承。相反,派生類將獲得多個繼承的建構函式,其中每個建構函式分別省略掉一個含有預設實參的形參。
例如,如果基類有一個接受兩個形參的建構函式,其中第二個形參含有預設實參,則派生類將獲得兩個建構函式:一個建構函式接受兩個形參(沒有預設實參),另一個建構函式只接受一個形參,它對應於基類中最左側的沒有預設值的那個形參。
- 如果基類含有幾個建構函式,則除了兩個例外情況,大多數時候派生類會繼承所有這些建構函式。
第一個例外是派生類可以繼承一部分建構函式,而為其他建構函式定義自己的版本。如果派生類定義的建構函式與基類的建構函式具有相同的引數列表,則該建構函式將不會被繼承。定義在派生類中的建構函式將替換繼承而來的建構函式。
第二個例外是預設、拷貝和移動建構函式不會被繼承。這些建構函式按照正常規則被合成。繼承的建構函式不會被作為使用者定義的建構函式來使用,因此,如果一個類只含有繼承的建構函式,則它也將擁有一個合成的預設建構函式。