C++多重继承与虚基类

C++多重继承与虚基类

1.多重继承

多重继承就是一个派生类继承了多个基类。

I>初始化

i>通过成员初始化列表指定构造函数

class D : public B, public C {
private:
    int a;
public:
    D(int a1, int a2, int a3) : B(a1),C(a2),a(a3) {
        cout << "D::D()" << endl;
    }
};

ii>构造函数调用顺序

class B {
private:
    int a;
public:
    B(int a) : a(a){ 
        cout << "B::B()" << endl; 
    }
    void f() {
        cout << "B::f() ------ a = " << a << endl;
    }
};

class C {
private:
    int a;
public:
    C(int a) : a(a) {
        cout << "C::C()" << endl;
    }
    void f() {
        cout << "C::f() ------ a = " << a << endl;
    }
};

class D : public B, public C {
private:
    int a;
public:
    D(int a1, int a2, int a3) : a(a3),B(a1),C(a2) {
        cout << "D::D()" << endl;
    }
    void f() {
        B::f();
        C::f();
        cout << "D::f() ------ a = " << a << endl;
    }
};


int main() {

    D obj(1, 2, 3);
    obj.f();
    return 0;
}

输出:

B::B()
C::C()
D::D()
B::f() ------ a = 1
C::f() ------ a = 2
D::f() ------ a = 3

调用构造函数的顺序与继承顺序一致

II>解决冲突

i>方法冲突

派生类中重定义方法(用上例的方法)

void D::f(){
    B::f();
    C::f();
    ...
}

或者在外部调用的时候加上域解析运算符

objD.B::f();//派生类对象调用基类B的方法f
objD.C::f();//派生类对象调用基类C的方法f

ii>成员冲突(经典的菱形继承)

C++多重继承与虚基类

class A {
private:
    int a;
    ...
};

class B : public A{
    ...
};

class C : public A{
    ...
};

class D : public B, public C {
    ...
};

可以看到,对于类A的成员,继承了两次,也就是说,D中,有两个重复成员a的来自类A,这是不必要的,所以,有了新的继承方法,虚继承

2.虚基类

I>虚继承

在继承时,加上virtual关键字,表示虚继承

class A {

};
class B : virtual public A {

};

int main() {

    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;
    return 0;
}

输出:

1
4

可以发现,类B中有了隐藏的成员(如果是普通的继承,只占1个字节),其实,这个隐藏的成员存储了类B对象到类A对象的一个间接引用(存了一个整数,是一个偏移量)

虚继承中的基类被称为虚基类

II>通过虚继承解决成员冲突问题

class A {
private:
    int a;
    ...
};

class B : virtual public A{
    ...
};

class C : virtual public A{
    ...
};

class D : public B, public C {
    ...
};

通过虚继承,当D创建实例时,只会继承“一份A”(B,C中存了间接引用)。

III>虚基类的初始化——间接派生类的初始化成员列表

D::D(int a) : A(a) {...}

注意,A是D的间接基类,如果A是非虚基类,这里是非法的,一般情况下,只能指定自己的直接基类怎样调用构造;如果是虚基类,想要调用有参的构造,就要在间接基类中的成员初始化列表中指定虚基类调用哪个构造。不能通过B和C的构造函数进行传递,因为只有一份A,两条初始化路径可能会造成二义性,所以,采用这样的方式初始化虚基类。

IV>虚基类还改变了对于方法二义性的解释——派生类的名称优先于直接或间接基类的名称

先来看普通的继承:

class A {
public:
    void f() {
        cout << "A::f()" << endl;
    }
};
class B : public A {
public:
    void f() {
        cout << "B::f()" << endl;
    }
};

class C : public A {
};

class D : public B, public C{

};

int main() {

    D objd;
    objd.f();
    return 0;
}

这里会出现二义性调用,D中有B::f()和A::f()

对于虚继承:

class A {
public:
    void f() {
        cout << "A::f()" << endl;
    }
};
class B : virtual public A {
public:
    void f() {
        cout << "B::f()" << endl;
    }
};

class C : virtual public A {
};

class D : public B, public C{

};

int main() {

    D objd;
    objd.f();
    return 0;
}

输出:

B::f()

同样,D中有B::f()和A::f(),但B是A的子类,则,B::f更优先。 个人觉得在使用时还是加上域解析运算符,这样不容易出错