C++虚继承(理解还不到位)

来源:互联网 发布:protobuf使用教程php 编辑:程序博客网 时间:2024/05/15 19:54
  • 无论继承的层级有多少层,只要他们之间保持着直接或者间接的继承关系,子类都可以与直接父类或者间接父类成为IsA的关系,并且能通过特定的指针对直接子类或间接子类进行相应的操作
  • 虚继承
    当出现菱形继承时,一个子类可能在追溯父类时找到了同一个父类,这时参数会重复,需要虚继承来解决。
    举个例子(以下来自百度百科):
    #include <iostream.h>#include <memory.h>class CA{int k; //如果基类没有数据成员,则在这里多重继承编译不会出现二义性public:void f() {cout << "CA::f" << endl;}};class CB : public CA{};class CC : public CA{};class CD : public CB, public CC{};void main(){CD d;d.f();}
    当编译上述代码时,我们会收到如下的错误提示:
    error C2385: 'CD::f' is ambiguous
    即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。这里可能会让人觉得有些奇怪,命名只定义了一个CA::f,既然大家都派生自CA,那自然就是调用的CA::f,为什么还无法确定呢?
    这是因为编译器在进行编译的时候,需要确定子类的函数定义,如CA::f是确定的,那么在编译CB、CC时还需要在编译器的语法树中生成CB::f,CC::f等标识,那么,在编译CD的时候,由于CB、CC都有一个函数f,此时,编译器将试图生成这两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译)
    要解决这个问题,有两个方法:
    1、重写函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;
    此时CD的元素结构如下:
    |CB(CA)|
    |CC(CA)|
    故此时的sizeof(CD) = 8;(CB、CC各有一个元素k)
    2、使用虚继承:虚继承又称作共享继承,这种共享其实也是编译期间实现的,当使用虚继承时,上面的程序将变成下面的形式:
    #include <iostream.h>#include <memory.h>class CA{int k;public:void f() {cout << "CA::f" << endl;}};class CB : virtual public CA //也有一种写法是class CB : public virtual CA{ //实际上这两种方法都可以};class CC : virtual public CA{};class CD : public CB, public CC{};void main(){CD d;d.f();}
    此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:
    |CB||CC||CA|
    同时,在CB、CC中都分别包含了一个指向CA的虚基类指针列表vbptr(virtual base table pointer)(虚基表指针),其中记录的是从CB、CC的vbtable的首地址(vbptr)到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重写了该函数,从而达到“共享”的目的。
    也正因此,此时的sizeof(CD) = 12(vbptrCB + vbptrCC + sizoef(int))(32位机中指针占4个字节);
    另注:
    如果CB,CC中各定义一个int型变量,则sizeof(CD)就变成20(两个vbptr + 3个sizoef(int)
    如果CA中添加一个virtual void f1(){},sizeof(CD) = 16(vfptrCA +vbptrCB + vbptrCC + sizoef(int));
    再添加virtual void f2(){},sizeof(CD) = 16不变。原因如下所示:带有虚函数(大于等于1个)的类,其内存布局上包含一个指向虚函数列表的指针(vfptr)(虚函数表指针),这跟该类有几个虚函数无关。
    一个典型的菱形继承使用的初始化列表:
    MigrantWorker::MigrantWorker(string name, string code, string color) :       Farmer(name, color),       Worker(code, color) //, Person(color)
  • 传参使用初始化列表,如果父类的构造函数有其他逻辑,则无需在子类的构造函数中重复书写!!!
  • 重定义
    重定义是指在当前的工程中,一个类被定义了两遍,只在多个继承类的.h文件中,多次引用了基类.h的文件,这就出现了重定义。可以通过宏定义解决重定义。
    用宏定义来解决重定义:
#ifndef PERSON_H
#define PERSON_H
//中间是防止重定义的类的实现。
#endif // DEBUG
  • 虚析构函数和虚继承解决的是完全不同两个问题 
    虚析构函数解决的是父类被子类初始化后在进行内存回收是防止内存泄露而出现的解决方案
    虚继承则是为解决菱形继承时出现父类的菱形继承而出现的解决方案
  • 虚继承是为了解决菱形继承,但是重定义不一定是虚继承造成的问题,要分清楚这两点
原创粉丝点击