多重继承的构造


如〈继承共同行为〉中看过的,在单一继承时,情况比较单纯,构造子类实例时,会先执行父类构造函数,接着是子类构造函数,而解构的时候相反,会先执行子类析构函数,接着才是父类析构函数。

多重继承时,若继承来源之一有状态定义,另一个没有状态定义,就如〈纯虚拟函数(二)〉、〈模版与继承〉中的范例,因为另一来源没有状态定义,也就不用考虑该来源的初始化或销毁问题,这时只要考量有状态定义的继承来源的构造与解构,如同单一继承,问题就可以单纯化。

在进一步看到多重继承的构造与解构之前,先来看个单一继承时this实际地址在哪的示范:

#include <iostream>
using namespace std;

class P {
    int x;

public:
    P(int x) : x(x) {
        cout << "P:" << this << endl;
    }
};

class C : public P {
public:
    C(int x) : P(x) {
        cout << "C:" << this << endl;
    }
};

int main() { 
    C c(10);

    cout << &c << endl;

    return 0;
}

显示的结果会是同一地址:

P:0x61febc
C:0x61febc
0x61febc

如果是多重继承的话呢?

#include <iostream>
using namespace std;

class P1 {
    int x;

public:
    P1(int x) : x(x) {
        cout << "P1:" << this << endl;
    }
};

class P2 {
    int x;

public:
    P2(int x) : x(x) {
        cout << "P2:" << this << endl;
    }
};

class C : public P1, public P2 {
public:
    C(int x) : P1(x), P2(x) {
        cout << "C:" << this << endl;
    }
};

int main() { 
    C c(10);

    cout << &c << endl;

    return 0;
}

多重继承时,构造函数的执行顺序会与继承的顺序有关(而不是调用父类构造函数的顺序),C因为继承时的顺序是P1P2,构造函数执行顺序会是P1P2C,至于析构函数的执行顺序,会是与构造函数执行相反的顺序,从执行结果中,可以发现this的地址会是不同:

P1:0x61feb8
P2:0x61febc
C:0x61feb8
0x61feb8

多重继承时,C实例的起始地址是 0x61feb8,而P1地址的偏移量是 0,P2地址的偏移量是 4,因此P1P2中虽然都定义了x成员,若在个别的类中写this->x,因为this地址不同,获取就会是各自不同的x,因为是各自不同的地址,构造时也是个别地初始化化在不同的地址,从执行结果中也可以看到,派生类实例的地址会用来初始化第一个继承的父类。

多重继承时,个别类中的this地址不同的事实,也会反应在以父类类型参考子类实例之时:

...略

int main() { 
    C c(10);

    cout << &c << endl;

    P1 &p1 = c;
    P2 &p2 = c;

    cout << "p1:" << &p1 << endl;
    cout << "p2:" << &p2 << endl;

    return 0;
}

执行时有关p1p2地址的显示结果会是不同的:

...
p1:0x61feb0
p2:0x61feb4

取址的时候也是,在以下的范例中,都是&c,然而p1p2存储的地址并不同,知道这个事实后,就会知道将p1指针的地址指定给p2是不可行的,会造成编译错误:

... 略

int main() { 
    C c(10);

    P1 *p1 = &c;
    P2 *p2 = &c;

    p2 = static_cast<P2*>(p1); // error: invalid static_cast from type 'P1*' to type 'P2*'

    return 0;
}

如果强硬地使用 C 风格转型,也就是p2 = (P2*) p1的话,虽然可以通过编译,结果是造成p2存储了p1的地址,虽然p2的类型是P2*,若透过p2操作P2定义的方法,会造成方法中的this指向的是p1的地址,结果会是不可预期的,当然,P1P2本来就是不同继承体系、不同类型,试图在两者之间转换,本来就是错误的。


展开阅读全文