OOP继承问题,虚基类, 建构子的构建顺序问题

来源:互联网 发布:java编程工具安卓 编辑:程序博客网 时间:2024/05/16 20:31

这里我们说说多重继承(multiple inheritance)。 多重继承的问题涉及到建构子和析构子的调用顺序问题, 多重继承造成的歧义型(例如, 当多个base classes中有同名函数的时候, 如何解决,members from common base class如何解决(dimond的时候, 即类A继承自B, C, 但是B, C又同时继承自一个类D时候, 就出现歧义了, 此时A有两份关于D的拷贝))。 当涉及到vitual base class(虚基类)的时候, 初始化的顺序和调用顺序如何呢??

首先, 定义一个class具有多个parent class 很简单, 只需要list them one by one:

<span style="font-size:14px;">class Boo: public Bum, public Foo {    // specify its own properties};</span>
这样, 一个Boo类的object可以upcast(向上转型)为 Bum, 或者Foo, 因为Boo继承了其父类的一切, 虽然object无法看到父类的private的部分。

当我们创建一个Boo的object的时候, 需要先调用Bum 和 Foo的default constructor, 然后在调用自己的。 也就是说,先构造父亲的那一部分, 调用析构子的顺序和调用构造子的顺序相反

如下:

#include <iostream>using namespace std;class Sofa {public:    Sofa() {        cout << "构造Sofa" << endl;    }    ~Sofa() {        cout << "析构Sofa" << endl;    }    void sit() {        cout << "sit" << endl;    }};class Bed {public:    Bed() {        cout << "构造Bed" << endl;    }    ~Bed() {        cout << "析构Bed" << endl;    }    void lie() {        cout << "lie" << endl;    }};class Sofabed: public Bed, public Sofa { //按照list的顺序构建, 即继承的顺序public:    Sofabed() {        cout << "构造Sofabed" << endl;    }    ~Sofabed() {        cout << "析构Sofabed" << endl;    }    void sitAndLie() {        cout << "sit and lie" << endl;    }};int main() {    Sofabed myfur;    cout << endl;    myfur.sit(); //继承的    myfur.lie(); //继承的    myfur.sitAndLie(); // 自己的    return 0;}
运行结构如下:


可见, 先构造父类的, 然后才是自己的。 当有多个父亲的时候, 则按照继承的顺序构造出来。


但是, 多重继承有很多的缺点, 不建议使用。 

首先, 如果有两个parent classes有same name的members的时候, 就不好了。 此时当要使用这些同名的函数的时候, 我们只能用resolution operator解决冲突。 很不好。

另外, 只使用单继承更加的easier, less prone to error, 所以最好不要使用多重继承啦。

因为多重继承可以改写为单继承。 例如Sofabed可以单继承Sofa, 而Sofa里面有一个Bed的class。 

不建议使用多继承
Anyway, 我们在多说一下多重继承。

派生类(derived class)必须为每一个继承的base class的建构子提供初始化的参数。 例如如下:

<span style="font-size:14px;">Cderived::Cderived(arg_B1, arg_B2, ..., argBn, arg_Cderived):    B1(arg_B1), B2(arg_B2), ..., Bn(arg_Bn) {        // initialize Cderived's own data members    }</span>
注意, 基类的建构顺序是以派生类继承的顺序调用的,而不是这里的初始化列表的顺序。
当派生类调用自己的defalut copy constructor的时候, 编译器(compiler)会自动的调用基类的default copy constructor。

当复制构造函数是你自己定义的时候, 你就必须把相对应的参数传递给基类的copy constructor。

<span style="font-size:14px;">Cderived::Cderived(Cdedrived &c1): B1(c1), B2(c1), ..., B(c1) {    // copy the rest data members}</span>

多重继承造成的歧义性有如下 两种情况:

(1)如果A继承了B, 和C, 但是B, C类中定义了同一个名字的函数func(), 此时A有两个同名的函数。 解决歧义的办法是使用B::func() 和 C::func()标识, 即resolution operator。 另外, 如果我们什么基类例如B型的一个pointer ptr的时候,然后将ptr指向派生类的时候, 用ptr指向派生类A的某个成员(假如这个成员在B中也有), 此时ptr指到的不是派生类A的这个成员, 而是基类的。 因为ptr会根据自己的type(为B)去调用相关函数(尽管ptr指向的地址是A的对象)。 此时的解决办法就是把这个同时出现在A和B的成员函数声明为virtual的(虚函数)。

(2) A继承了B,和C, 但是B, C 继承了同一个D, 此时A就继承了两份D的拷贝, 形成diamond的shape。 一份来自于B, 一份拷贝来自于C. 不仅造成在调用common base class的成员函数产生的歧义性, 而且浪费了空间。 解决的办法就是使用virtual base class(虚基类)。 即将D声明为虚基类(被其他类虚继承), 这样在做common base class的时候就只有一份拷贝了。

如下:

<span style="font-size:14px;">class CA { // common base classpublic:    int x;    CA(int a = 0) { x = a;}};clas CB: virtual public CA {public:    int y;    CB(int a, int b= 0): CA(a) { y = b}; // 注意CA(a)不能省略, 下同}class CC: virtual public CA {public:    int z;    CC(int a = 0, int b = 0): CA(a) { z = b;}};class CD: public CB, public CC {public:     int w;    CD(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0):CA(a), CB(a, b), CC(c, d) { // 注意只有CA(a)对x的设置有效, 后面的CB,CC无效        w = e;    }    };</span>

下面设置的时候:

<span style="font-size:14px;">CD obj(5, 4, 3, 2, 1);// x 为5, 后面的设置就无效</span>
宣告为虚基类的时候, CA的构造函数值执行了一次。

为了保证虚基类的正确性, 我们最好首先调用virtual base class 的constructorfirst。 这样, 后面调用徐基类的constructor就无效了。 虚基类保证虚基类的建构子只执行一次。 (继承发生了, 才有虚基类的概念)。


建构子的初始化问题:

(1) 首先调用virtual class 的 建构子,有多个虚基类的时候, 按照虚基类的声明顺序, 而不是初始化顺序。

(2)然后按照普通的基类的声明顺序调用建构子(即继承关系声明的顺序)。  

例如下例:

<span style="font-size:14px;">#include <iostream>using namespace std;class C1 {public:    C1() {cout << "建构C1\n";}    ~C1() {cout << "析构C1\n";}};class C2 {public:    C2() {cout << "建构C2\n";}    ~C2() {cout << "析构C2\n";}};class C3 {public:    C3() {cout << "建构C3\n";}    ~C3() {cout << "析构C3\n";}};class C4 {public:    C4() {cout << "建构C4\n";}    ~C4() {cout << "析构C4\n";}};class CD: public C3, virtual public C4, virtual public C2 { // 宣告顺序private:    C1 obj; // C1 的objectpublic:    CD(): obj(), C2(), C3(), C4() {cout << "建构CD\n";}// 初始化顺序    ~CD() {cout << "析构CD\n";}};int main() {    CD dd;    cout << "here\n" << endl;    return 0;}</span>

执行结果如下:


可见建构的顺序是: 先虚基类, 多个虚基类按照宣告的顺序。 然后是其他的普通基类, 按照宣告的顺序, 最后是自己的成员里面的对象建构(如果有class作为其成员, 按照宣告的顺序), 最后调用自己的析构函数,

如果改为里面有static 的class的object的时候, 无论如何要先把static的最先构造出来, 如果这个static又继承自某个父类, 同理, 先构造器父类, 如下:

<span style="font-size:14px;">#include <iostream>using namespace std;class C1 {public:    C1() {cout << "建构C1\n";}    ~C1() {cout << "析构C1\n";}};class C2 {public:    C2() {cout << "建构C2\n";}    ~C2() {cout << "析构C2\n";}};class C3 {public:    C3() {cout << "建构C3\n";}    ~C3() {cout << "析构C3\n";}};class C4 {public:    C4() {cout << "建构C4\n";}    ~C4() {cout << "析构C4\n";}};class CD: public C3, virtual public C4, virtual public C2 { // 宣告顺序private:    C1 obj; // C1 的object    C2 obj2;    static C3 obj3; public:    CD(): obj(), C2(), C3(), C4() {cout << "建构CD\n";}// 初始化顺序    ~CD() {cout << "析构CD\n";}};C3 CD::obj3; // 奇怪, 如果没有这一行, 即注释掉, 则obj3不会被构造出来!!!!!int main() {    CD dd;    cout << "here\n" << endl;    return 0;}</span>
运行如下:



2 0
原创粉丝点击