构造语义学

继承构造函数

类具有可派生性,派生类可以自动的获取基类的成员变量和接口(虚函数和纯虚函数,public派生)。不过基类的废墟函数则无法再被派生类使用了。这条规则对于构造函数也不例外。如果派生类要使用基类的构造函数,通常需要在构造函数中显式声明。

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

如果基类有数量较多的不同版本的构造函数,但是派生类却只有少量的构造函数时。这时,这种透传的方案就显得不是方便。

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;
    /*
     * 声明派生类中使用基类的函数 f
     * 这样一来,派生类就有了两个 f 函数的版本。
    */
    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 使用在构造函数上:

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

struct B : A {
    using A::A;     // 继承构造函数
    // ...
    virtual void ExtraInterface(){}
};

C++11 标准继承构造函数被设计为跟派生类中的默认函数(默认构造,析构,拷贝构造等)一样,是隐式声明的。这意味着如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。

不过继承构造只会初始化基类的成员变量,对于派生类中的成员变量则无能为力。

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
}

有时,基类构造函数参数会有默认值,对于继承构造函数来讲,参数的默认值是不会被继承的。事实上,默认值会导致基类产生多个构造函数的版本,这些函数版本都会被派生类继承。

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

事实上,A会产生如下构造函数:

  • A(int a = 3, double = 2.4)
  • A(int a = 3)
  • A(const A&) 默认的复制构造
  • A()

相应的,B中的构造函数也会包括如下:

  • B(int , double) 继承构造
  • B(int) 继承构造
  • B(const B&) 复制构造,不是继承来的
  • B() 默认构造

继承构造函数“冲突”的情况:

多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数名,参数都相同,那么继承类中的冲突的继承构造函数将导致不合法的派生类代码。

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

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

一旦使用了继承构造函数,那么编译器就不会为派生类生成默认构造函数了。

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

B b; // error , 没有默认无参构造函数

委派构造函数

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'};
    // ...
};

上述示例进行了优化,但是还是需要调用 InitReset() .

C++11 委派构造函数

class Info {
public:
    Info() { InitRest(); }
    Info(int i) : Info() { type = i; }
    Info(char e): Info() { name = e; }
    /*
     * 委派构造函数不能有初始化列表
    */

private:
    void InitRest() { /* 其它初始化 */ }
    int  type {1};
    char name {'a'};
    // ...
};
  • 调用者 —— 委派构造函数(delegating constructor)
  • 被调用者 —— 目标构造函数(target constructor)

继续改造:

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) { }    // 委托构造函数
    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;
    // ...
};

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

struct Rule2 {
    int i, c;

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

委派构造函数一个实际的应用:使用构造模板函数产生目标构造函数

#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()) {}
};

委派构造函数在异常处理中的应用

#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);
}

从目标函数中抛出异常,在委派构造函数中捕获。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容