构造语义学

继承构造函数

类具有可派生性,派生类可以自动的获取基类的成员变量和接口(虚函数和纯虚函数,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 
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 
#include 
#include 
using namespace std;

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

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

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

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

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

你可能感兴趣的:(构造语义学)