第11課 繼承構造函數和委派構造函數

1、繼承構造函數ios

(一)函數繼承同名覆蓋c++

  1. 派生類能夠自動得到基類的成員變量和接口(虛函數和純虛函數,這裏指都是public派生),這體現了類的繼承性。編程

  2. 若是基類的非虛函數在派生類被重寫,則將發生同名覆蓋現象。即基類函數在派生類中會被同名函數隱藏,從而不可見特別是構造函數也不例外,基類的構造函數在派生類中將不可見,若是須要這些構造函數,則需在派生類中顯式聲明這些接口。函數

(二)繼承構造函數this

  1. C++0x中若是派生類要使用基類成員函數,能夠經過using聲明來完成。C++11中,這種用法被擴展到構造函數,即子類能夠經過使用using來聲明繼承基類的構造函數。可是一旦使用繼承構造函數,編譯器就不會再爲派生類生成默認構造函數了。編碼

  2. 繼承構造函數只會初始化基類中的成員變量,對於派生類中的成員變量則需由派生類自行初始化(如「就地初始化」、初始化列表等)。spa

  3. 若是基類構造函數的參數有默認值,編譯器會產生多個構造函數,並被派生類繼承(含默認值)設計

  4. 在多繼承體系中,若是某些基類構造函數參數個數和類型都相同,那麼在繼承構造函數時會發生「衝突,能夠在派生類中顯式定義那些「衝突」的構造函數,從而阻止隱式生成相應的繼承構造函數來解決衝突。code

  5. 若是基類的構造函數被聲明爲private,或者派生類是從基類中虛繼承的,就不能在派生類中聲明繼承構造函數對象

  6. C++11中繼承構造函數與派生類的各類類默認函數同樣,是隱式聲明的。即若是一個繼承構造函數不被相關代碼使用,編譯器不會爲其產生真正的函數代碼。這比顯式聲明函數更加節省目標代碼空間。

【編程實驗】繼承構造函數

#include <iostream>
using namespace std;

//1. 基類
struct Base
{
    Base() { cout << "Base()" << endl; }
    Base(int i) { cout << "Base(int i)" << endl; }
    Base(double d, int i) { cout << "Base(double d, int i)" << endl; }

    Base(float f, int i, const char* c) 
    {
        cout << "Base(float f, int i, const char* c) " << endl;
    }

    void func(double i)  //非虛函數
    {
        cout << "Base::func(double i) : " << i << endl;
    }
};

//1.1 顯式聲明構造函數
struct NonUsingDerived : public Base
{
    int d{ 0 }; //就會初始化派生類成員

    //注意:
    //1. 基類自定義了構造函數,編譯器再也不爲派生類提供默認構造函數
    //2. 基類的構造函數在派生類中是被隱藏的,沒法「透傳」給基類。爲了使用基類構
    //   造函數,需在派生類中一個一個地顯式聲明(明顯很笨拙),以下
    NonUsingDerived(int i): Base(i){ cout << "NonUsingDerived(int i)" << endl; }
    NonUsingDerived(double d, int i) : Base(d, i) { cout << "NonUsingDerived(double d, int i)" << endl; }

    NonUsingDerived(float f, int i, const char* c) : Base(f, i, c)
    {
        cout << "NonUsingDerived(float f, int i, const char* c) " << endl;
    }

    void func(int i)  //與基類非虛函數同名,這裏會將基類同名函數隱藏,從而不可見。
    {
        cout <<"NonUsingDerived::func(int) : " << i << endl;
    }
};

//1.2 使用using繼承構造函數
struct UsingDerived : public Base
{
    int d{ 0 }; //就會初始化派生類成員

    //注意:使用using繼承基類的構造函數 
    using Base::Base;  //繼承構造函數

    using Base::func; //使用基類void func(double)函數
    void func(int i)  //
    {
        cout << "UsingDerived::func(int) : " << i << endl;
    }   
};

//2. 帶默認值的構造函數
struct Parent
{
    int a;
    double d;
    Parent(int a = 3, double d = 2.4) : a(a), d(d)
    {
        cout << "Parent(int, double)" << endl;
    }
    void show()
    {
        cout <<"a = " << a << ", d = " << d<< endl;
    }
};

struct Child : public Parent
{
    //因爲基類構造函數帶默認參數值,使用using繼承時,其實分解爲
    //Parent(int,double)、Parent(int)、Parent()等並繼承下來(含默認值)。
    using Parent::Parent; //繼承基類的構造函數
};

int main()
{
    //1. 顯式聲明構造函數與using的比較
    //NonUsingDerived d1;  //編譯失敗,派生類沒顯式聲明無參構造函數。
    NonUsingDerived d2(1);
    d2.func(1);    //調用派生類本身的void func(int)
    d2.func(1.5);  //經隱式轉換,仍會調用派生類的void func(int),由於基類void func(double)被隱藏。

    UsingDerived d3; //ok, 繼承了基類的無參構造函數。
    UsingDerived d4(1.0, 2); 
    d4.func(1);   //調用派生類本身的void func(int)
    d4.func(1.5); //調用基類的void func(double)

    cout <<"----------------------------------------------------------------------------" << endl;
    //2. 帶默認值的構造函數的繼承
    Child c1;
    c1.show();

    Child c2(4);
    c2.show();

    Child c3(4, 1.0);
    c3.show();
    
    return 0;
}
/*輸出結果
Base(int i)
NonUsingDerived(int i)
NonUsingDerived::func(int) : 1
NonUsingDerived::func(int) : 1
Base()
Base(double d, int i)
UsingDerived::func(int) : 1
Base::func(double i) : 1.5
----------------------------------------------------------------------------
Parent(int, double)
a = 3, d = 2.4
Parent(int, double)
a = 4, d = 2.4
Parent(int, double)
a = 4, d = 1
*/

2、委派構造函數

(一)概述

  1. 在C++11中,所謂委派構造,就是指委派函數將構造任務委託給某個目標構造函數來完成類構造的一種方式。通俗地講,就是容許構造函數經過初始化列表方式來調用同一個類中的另外一個構造函數

  2. 委派構造函數和目標構造函數是調用者和被調用者的關係

  3. 在多構造函數類中,經過委派給其餘構造函數,將使編碼更簡潔。

  4. 目標構造函數是一個構造函數,而不是普通的成員函數。

(二)委派構造函數

  1. 做爲委派的目標構造函數,通常會抽象成最爲「通用」的基本版本,以方便其餘構造函數的調用。

  2. 委派構造函數在初始化列表位置進行構造和委派

  3. 在委派構造函數的初始化列表中只容許有目標構造函數,不能有其餘項的初始化。若是委派構造函數要給變量賦初值,初始化代碼必須放在函數體中。

  4. 目標構造函數老是先於委派構造函數執行(這是因爲C++中初始化列表老是先於構造函數被調用)。所以避免目標構造函數和委派構造函數體中初始化一樣的成員一般是必要的

  5. 當構造函數較多時,可能擁有不止一個委派構造函數,而一些目標構造函數極可能也是委派構造函數。所以,委派構造函數關係中造成鏈狀的構造關係,但不能造成「委派環」

(三)委派構造函數的應用

  1. 模板構造函數:經過構造函數模板來產生目標構造函數,再利用委派使構造函數具備泛型編程的能力

  2. 異常處理方面,若是在委派構造函數中使用try的話,那麼從目標構造函數產生的異常,均可以在委派構造函數中被捕捉到

    (1)這是一種在構造函數體外catch異常的方式,C++會自動從新將異常拋出給對象建立者,哪怕咱們沒有顯式throw該異常

    (2)這種構造函數體外的try-catch語句,能夠同時捕獲基類或數據成員拋出的異常。此外還有一個用途,就是用來轉換捕捉到的異常對象。

【編程實驗】委派構造函數

#include <iostream>
#include <vector>
#include <deque>
#include <list>
using namespace std;

//1. 委派構造函數
//1.1 不使用委派構造的類
class Info1
{
private:
    int type;
    char name;
    void initRest()
    {
        type += 1; //其它初始化任務
    }
public:
    //三個構造函數初除了初始化列表不一樣,函數體都是同樣的。若是initRest代碼量大,存在
    //嚴重的代碼重複現象。
    Info1() : type(1), name('a') { initRest(); }
    Info1(int i) : type(i), name('a') { initRest(); } //type(i)先於initRest執行!
    Info1(char c) : type(1), name{ 'e' }{initRest(); }

    void print()
    {
        cout << "type = " << type << ", name = " << name << endl;
    }
};

//1.2 使用委派構造函數
class Info2
{
private:
    int type;
    char name;

    //目標構造函數(這裏通常設爲private)
    Info2(int i, char e) : type(i),name(e)
    {
        type += 1;  //其它初始化
    }
public:
    //三個構造函數爲委派構造函數,會將構造任務委託相應的目標構造函數!
    Info2() : Info2(1, 'a') {}    //委派給Info2(int i, char e)目標函數去構造
    Info2(char c) : Info2(1, c){} //委派給Info2(int i, char e) 去構造

    //Info2(int i) : Info2(),type(i) {} //編譯失敗!委派構造初始化列表不能有目標構造函數之外的成員!
    Info2(int i) : Info2() { type = i;} //委派給Info2() 去目標構造函數

    void print()
    {
        cout << "type = " << type << ", name = " << name << endl;
    }
};

//2. 構造函數模板+委派,使構造函數具備泛型編程能力
class TDConstructed  //Template delegate construct
{
private:
    std::list<int> ls;

    //構造函數模板:構造一個容器,用於採集從first到last之間的元素(T爲迭代器類型)
    template<typename T> TDConstructed(T first, T last) : ls(first, last)
    {
    }
public:
    //利用委派,使該類能接受多種容器對其進行初始化。
    TDConstructed(vector<short>& v) : TDConstructed(v.begin(), v.end()) {}
    TDConstructed(deque<int>& d) : TDConstructed(d.begin(), d.end()) {}

    void print()
    {
        for (const auto& elem : ls)
        {
            cout << elem << " ";
        }
        cout << endl;
    }
};

class Widget
{
public:
    Widget() { cout << "Widget() : " << this << endl; }
    ~Widget() {cout << "~Widget() : " << this << endl;}
};

//3. 利用委派構造來捕獲構造函數出現的異常
class ExceptDemo  //Delegate Constructor Exception
{
private:
    int type;
    double data;
    Widget w;
    
    //目標構造函數
    ExceptDemo(int i, double d) : type(i), data(d)
    {
        cout << "going to throw!" << endl;
        throw 0;  
    }
public:
    //構造函數塊外的catch語句(上面這種)即便沒有顯示地從新拋出異常,c++也會自動拋出
    //委派給ExceptDemo(int, double)去構造。
    ExceptDemo(double d) try : ExceptDemo(1, d)   //注意,try塊是整個構造函數體,而異常是構造函數體外被catch!
    {                                             //這種方式能夠同時捕獲基類或數據成員拋出的異常!
        //目標構造函數拋出異常,這裏沒機會執行!這樣設計是合理的,當構造函數出現異常
        //不該該再繼續執行這段代碼去構造了。
        cout << "Run the body." << endl; 
        //其餘初始化
    }
    catch (...)
    {
        cout << "ExceptDemo(): caught exception." << endl; //捕獲到異常!!!執行到這裏。(注意,此時成員對象Widget會被釋放)

        //.....             //這裏能夠作一些清理工做,如delete一些堆對象操做,關閉一些句柄等操做
        //throw;            //標準的作法是:在這裏繼續傳遞異常給對象的建立者。但這種在構造函數塊外的catch語句,即便沒有
                            //顯式地從新拋出異常,c++也會自動將異常從新拋出。
    }

    ~ExceptDemo()
    {        
        cout <<"~ExceptDemo()" << endl;
    }
};

int main()
{
    //1. 委派構造函數和目標構造函數
    Info1 obj1(3);
    obj1.print();  //type = 4, name = a

    Info2 obj2(3);
    obj2.print();  //type = 3, name = a(因爲目標構造函數先執行,即type=4,但
                   //隨後執行委派構造函數,又執行type =i,即type又變成3。

    vector<short> v = { 1,2,3,4,5 };
    deque<int> d = { 6,7,8,9 };

    //2. 構造函數模板化
    TDConstructed td1(v);
    td1.print();

    TDConstructed td2(d);
    td2.print();

    //3. 在委派構造函數中捕獲構目標構造函數異常
    try {
        ExceptDemo ed(1.2);
    }
    catch (...)
    {
        cout <<"main():caught exception." << endl;
    }
    
    return 0;
}
/*輸出結果
type = 4, name = a
type = 3, name = a
1 2 3 4 5
6 7 8 9
Widget() : 003BF880
going to throw!
~Widget() : 003BF880
ExceptDemo(): caught exception.
main():caught exception.
*/
相關文章
相關標籤/搜索