C++编程思想杂记(②第9章 多重继承)

来源:互联网 发布:盗梦空间 bd50 淘宝 编辑:程序博客网 时间:2024/05/18 01:59

多重继承MI:继承多个基类来创建一个新类。目的是将多个类组合到一个类中,但其实现在有模板之后,使用类模板,也可以轻松的使用模板传入类型的接口。

所以现在MI基本上算是一种代码分解机制,最明显的例子就是ios,istream,ostream,iostream的菱形继承体系

1.接口继承

多重继承的一种应用就是接口继承,类似Java中的Interface,C++的继承其实都是实现继承,为了实现接口继承,需要令基类的函数除析构函数外都为纯虚函数(析构函数即是是纯虚函数你也需要提供定义..)。


这样就可以传递一个派生类对象来调用所有基类的纯虚函数在派生类中的实现,使用基类引用的接口。

如下代码

#include <iostream>
#include <string>
#include <typeinfo>
using namespace std;


class A
{
public:
    virtual ~A(){}
    virtual void print(ostream &)const = 0;
};
class B
{
public:
    virtual ~B(){}
    virtual void toB() const = 0;
};
class C
{
public:
    virtual ~C(){}
    virtual void toC() const = 0;
};
class D:public A,public B,public C
{
public:
    D(){}
    void print(ostream &t)const{t<<"A";}
    void toB() const{cout <<"b";}
    void toC() const{cout <<"c";}

};
void testPrint(A& a,B&b,C&c)
{
    a.print(cout);
    b.toB();
    c.toC();
}

int main()
{
    D d;
    testPrint(d,d,d);
    return 0;
}

多重继承的实现继承,可以使用模板替代而且感觉通用性更好。


多重继承的内存布局,按照public后接的类型顺序,在内存中布局,如public A,public B则内存布局是A,B,起始为A


如果将一个C指针向上转换为A指针,其指向地址和C指针一样,都是对象起始地址,如果是B指针,则会有指针的位移。

2.虚基类

多重继承的基类对象重复。对于继承,派生类对象中会包含一个基类对象,对于多重继承,如果父类的基类相同,则其派生子类中会包含多个最初基类对象。这种情况下,向上类型转换时,指针的位移有多种选择,这会出现问题。


所以针对菱形继承,需要使用虚基类,即比如B,C继承自A,D继承自B,C,则在B,C继承同一基类时,就需要这么写

class B:virtual public A

这样,可以使A的对象在D中只有一个,那么问题来了,这个A对象是在B还是C的构造函数中创建的?B和C都继承自A

所以,这里在D中直接调用A的构造函数创建这个A对象,而B和C中的构造函数中,调用A的构造函数传递的值为0,注意A的对象在整体对象的位置中在最后并且B和C中构造函数的A的构造函数实际上是不调用的.也就是说,虽然A,B,C构造函数中,一共调用了3次A的构造函数,但其实只调用了一次A的构造函数

如果不使用虚基类产生的D中,BC对象各包含一个A对象,然后如果向上类型转换,就会报错,表示基类无法转换因为不确定。


我们假设A,B,C,D中都包含一个int成员

class A
{
    int a;
public:
    A(int i):a(i){}
    
};
class B:virtual public A
{
    int b;
public:
    B(int i):A(i),b(i){}
    
    
};
class C: virtual public A
{
    int c;
public:
    C(int i):A(i),c(i){}
    
    
};
class D:public B,public C
{
    int w;
public:
    D(int i):A(i),B(i),C(i),w(i){}

};

这样没有虚析构函数其实是不对的,但是这种情况,D类对象的大小是24,如果有虚析构函数,则是28,排除16的int数据成员,还有8个字节,即2个指针,这是因为B和C都包含一个指针,指向唯一的A对象


这个图中,如果我们创建一个G类型对象,则调用构造函数的顺序是:

1.在最高层派生类G中BC虚基类的构造函数显式调用

2.对A,D类构造函数的调用

3.对E,F的构造函数的调用

4.G的构造函数的调用

但是在E,F中构造函数中,虚基类的构造函数不重复调用,中层派生类的共同基类的构造函数的显式调用是不被调用的。


3.多重继承中的名字冲突

即多个基类中的同名函数在派生类中的调用冲突。

如果同一派生层次基类中存在相同名称函数(相同返回值,参数列表),则在最高层派生类调用时,需要加上基类作用域标识

如果同名函数出现在不同派生层次基类,则会出现如下情况:某一基类的函数比另一基类的函数占优势


即在最高级别派生类中,较高层次派生类的同名函数会覆盖较低层次派生类的同名函数(相同返回值,参数列表)。如果是同层次不同基类,则要通过类作用域运算符来完成函数调用

这个继承体系中,假设A,B都包含一个void f(),则在G中如果调用f(),则会调用B::f()而不会是A::f()

0 0
原创粉丝点击