構(gòu)造語義學(xué)

繼承構(gòu)造函數(shù)

類具有可派生性,派生類可以自動(dòng)的獲取基類的成員變量和接口(虛函數(shù)和純虛函數(shù),public派生)。不過基類的廢墟函數(shù)則無法再被派生類使用了。這條規(guī)則對(duì)于構(gòu)造函數(shù)也不例外。如果派生類要使用基類的構(gòu)造函數(shù),通常需要在構(gòu)造函數(shù)中顯式聲明。

struct A {
    A(int i) {}
};
struct B: A {
    B(int i) : A(i) {};
}

如果基類有數(shù)量較多的不同版本的構(gòu)造函數(shù),但是派生類卻只有少量的構(gòu)造函數(shù)時(shí)。這時(shí),這種透傳的方案就顯得不是方便。

struct A { 
    A(int i) {}
    A(double d, int i) {}
    A(float f, int i, const char* c) {}
    // ...
};

struct B : A { 
    B(int i): A(i) {} 
    B(double d, int i) : A(d, i) {}
    B(float f, int i, const char* c) : A(f, i, c){}
    // ...
    virtual void ExtraInterface(){}
};

通過使用 using 聲明(using-declaration)來完成。

#include <iostream>
using namespace std;

struct Base {
    void f(double i) { cout << "Base:" << i << endl; }
};

struct Derived : Base {
    using Base::f;
    /*
     * 聲明派生類中使用基類的函數(shù) f
     * 這樣一來,派生類就有了兩個(gè) f 函數(shù)的版本。
    */
    void f(int i) { cout << "Derived:" << i << endl; }
};

int main() {
    Base b;
    b.f(4.5);  // Base:4.5

    Derived d;
    d.f(4.5);  // Base:4.5
}

using 使用在構(gòu)造函數(shù)上:

struct A { 
    A(int i) {}
    A(double d, int i) {}
    A(float f, int i, const char* c) {}
    // ...
};

struct B : A {
    using A::A;     // 繼承構(gòu)造函數(shù)
    // ...
    virtual void ExtraInterface(){}
};

C++11 標(biāo)準(zhǔn)繼承構(gòu)造函數(shù)被設(shè)計(jì)為跟派生類中的默認(rèn)函數(shù)(默認(rèn)構(gòu)造,析構(gòu),拷貝構(gòu)造等)一樣,是隱式聲明的。這意味著如果一個(gè)繼承構(gòu)造函數(shù)不被相關(guān)代碼使用,編譯器不會(huì)為其產(chǎn)生真正的函數(shù)代碼。

不過繼承構(gòu)造只會(huì)初始化基類的成員變量,對(duì)于派生類中的成員變量則無能為力。

struct A {
    A(int i) {}
    A(double d, int i) {}
    A(float f, int i, const char* c) {}
    // ...
};

struct B : A { 
    using A::A;
    int d {0};
};

int main() {
    B b(356);   // b.d被初始化為0
}

有時(shí),基類構(gòu)造函數(shù)參數(shù)會(huì)有默認(rèn)值,對(duì)于繼承構(gòu)造函數(shù)來講,參數(shù)的默認(rèn)值是不會(huì)被繼承的。事實(shí)上,默認(rèn)值會(huì)導(dǎo)致基類產(chǎn)生多個(gè)構(gòu)造函數(shù)的版本,這些函數(shù)版本都會(huì)被派生類繼承。

struct A {
    A(int a = 3, double = 2.4) {}
};
struct B : A {
    using A::A;
};

事實(shí)上,A會(huì)產(chǎn)生如下構(gòu)造函數(shù):

  • A(int a = 3, double = 2.4)
  • A(int a = 3)
  • A(const A&) 默認(rèn)的復(fù)制構(gòu)造
  • A()

相應(yīng)的,B中的構(gòu)造函數(shù)也會(huì)包括如下:

  • B(int , double) 繼承構(gòu)造
  • B(int) 繼承構(gòu)造
  • B(const B&) 復(fù)制構(gòu)造,不是繼承來的
  • B() 默認(rèn)構(gòu)造

繼承構(gòu)造函數(shù)“沖突”的情況:

多個(gè)基類中的部分構(gòu)造函數(shù)可能導(dǎo)致派生類中的繼承構(gòu)造函數(shù)的函數(shù)名,參數(shù)都相同,那么繼承類中的沖突的繼承構(gòu)造函數(shù)將導(dǎo)致不合法的派生類代碼。

struct A { A(int) {} };
struct B { B(int) {} };

struct C: A, B {
    using A::A;
    using B::B;
};

一旦使用了繼承構(gòu)造函數(shù),那么編譯器就不會(huì)為派生類生成默認(rèn)構(gòu)造函數(shù)了。

struct A { A(int){} };
struct B: A { using A::A };

B b; // error , 沒有默認(rèn)無參構(gòu)造函數(shù)

委派構(gòu)造函數(shù)

class Info {
public:
    Info() : type(1), name('a') { InitRest(); }
    Info(int i) : type(i), name('a') { InitRest(); }
    Info(char e): type(1), name(e) { InitRest(); }

private:
    void InitRest() { /* 其它初始化 */ }
    int  type;
    char name;
    // ...
};

上述示例明顯顯得非常笨拙:

class Info {
public:
    Info() { InitRest(); }
    Info(int i) : type(i) { InitRest(); }
    Info(char e): name(e) { InitRest(); }

private:
    void InitRest() { /* 其它初始化 */ }
    int  type {1};
    char name {'a'};
    // ...
};

上述示例進(jìn)行了優(yōu)化,但是還是需要調(diào)用 InitReset() .

C++11 委派構(gòu)造函數(shù)

class Info {
public:
    Info() { InitRest(); }
    Info(int i) : Info() { type = i; }
    Info(char e): Info() { name = e; }
    /*
     * 委派構(gòu)造函數(shù)不能有初始化列表
    */

private:
    void InitRest() { /* 其它初始化 */ }
    int  type {1};
    char name {'a'};
    // ...
};
  • 調(diào)用者 —— 委派構(gòu)造函數(shù)(delegating constructor)
  • 被調(diào)用者 —— 目標(biāo)構(gòu)造函數(shù)(target constructor)

繼續(xù)改造:

class Info {
public:
    Info() : Info(1, 'a') { }
    Info(int i) : Info(i, 'a') { }
    Info(char e): Info(1, e) { }

private:
    Info(int i, char e): type(i), name(e) { /* 其它初始化 */ }
    int  type;
    char name;
    // ...
};
class Info {
public:
    Info() : Info(1) { }    // 委托構(gòu)造函數(shù)
    Info(int i) : Info(i, 'a') { } // 既是目標(biāo)構(gòu)造函數(shù),也是委托構(gòu)造函數(shù)
    Info(char e): Info(1, e) { }

private:
    Info(int i, char e): type(i), name(e) { /* 其它初始化 */ } // 目標(biāo)構(gòu)造函數(shù)
    int  type;
    char name;
    // ...
};

避免形成“委托換(delegation cycle)”

struct Rule2 {
    int i, c;

    Rule2() : Rule2(2) {}
    Rule2(int i) : Rule2('c') {}
    Rule2(char c) : Rule2(2) {}
}

委派構(gòu)造函數(shù)一個(gè)實(shí)際的應(yīng)用:使用構(gòu)造模板函數(shù)產(chǎn)生目標(biāo)構(gòu)造函數(shù)

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

class TDConstructed {
    template<class T>
    TDConstructed(T first, T last)
        :l(first, last) {}
    list<int> l;

public:
    TDConstructed(vector<short> & v):
        TDConstructed(v.begin(), v.end()) {}
    TDConstructed(deque<int> & d):
        TDConstructed(d.begin(), d.end()) {}
};

委派構(gòu)造函數(shù)在異常處理中的應(yīng)用

#include <iostream>
using namespace std;

class DCExcept {
public:
    DCExcept(double d)
        try : DCExcept(1, d) {
            cout << "Run the body." << endl;
            // 其它初始化
        } catch(...) {
            cout << "caught exception." << endl;
        }
private:
    DCExcept(int i, double d) {
        cout << "going to throw!" << endl;
        throw 0;
    }
    int type;
    double data;
};

int main() {
    DCExcept a(1.2);
}

從目標(biāo)函數(shù)中拋出異常,在委派構(gòu)造函數(shù)中捕獲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容