重学C++系列之虚继承

一、什么是虚继承

        虚继承是C++中一种继承的机制。继承有普通继承虚继承两种机制,默认是普通继承,如果要使用多继承,需要在继承方式里加上关键字virtual。

二、为什么要虚继承

         在C++中,为解决二义性问题,引入虚继承机制。

        注意:虚继承是在多继承中讲的,而虚函数是在多态中讲的。

三、虚继承的特性

        虚继承使得公共的基类在派生类中只有一份,虚继承在多重继承的基础上多了vtable表(虚表)来存储到公共基类的偏移,而在使用公共基类时,通过vptr指针(虚指针)查表得到,该虚指针和虚表的维护由系统来进行。

 四、虚继承的原理

         C++在设计虚继承机制中,提供一个vtable(虚表)和vptr(虚指针)。虚指针指向虚表,虚表就是用来存放虚基类(祖父类)成员的地址。

        基类在采用虚继承时,编译器就会给这个类分配一个vptr(虚指针,32位地址空间占4个字节,64位地址空间占8个字节),要访问虚基类的成员时,就通过查表,虚基类的成员只保留一份,不管谁来访问,就是通过查表,虚表和虚指针都是通过编译器自身维护。

        由于虚基类的成员只保留一份,从整体来说,虚继承比普通多继承更加节省空间。

 五、实现虚继承的语法

        在多继承时,基类去继承虚基类时,使用关键字(virtual),再加上继承方式 虚基类。语法如下:

class 父类:virtual 祖父类    // 虚继承
{

};

class 子类:继承方式 父类1, 继承方式 父类2...(普通多继承)
{

};

六、案例

        1、验证虚指针的存在

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A1
{
    int a;
};

// 基类B1,虚继承
class B1:virtual public A1
{
    int b;

};

// 基类C1,虚继承
class C1:virtual public A1
{
    int c;
};

class Test1:public B1, public C1
{
    int data;
};


// 基类B2, 普通继承
class B2:public A1
{
    int b;

};

// 基类C2,普通继承
class C2:virtual public A1
{
    int c;
};

class Test2:public B2, public C2
{
    int data;
};


int main()
{
    Test1 tmp1;
    Test2 tmp2;
   
    // 虚继承后,多一个虚指针
    cout << "sizeof(tmp1): " << sizeof(tmp1) << endl;  

    // 虚基类只有单个成员变量时,比普通多继承占用内存空间少一个虚指针的字节
    cout << "sizeof(tmp2): " << sizeof(tmp2) << endl;   

    // 64位地址空间,指针占8个字节; 32位地址空间,指针占4个字节
    cout << "sizeof(int*): " << sizeof(int*) << endl;   
    
    return 0;
}

        2、初始化虚基类成员时,默认调用无参的构造函数

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A
{
private:
    int a;
public:
    A(int a = 0)
    {
        cout << "A()" << endl;
        this->a = a;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void show()
    {
        cout << "a = " << a << endl;
    }
};

// 基类B,虚继承
class B:virtual public A
{
private:
    int b;
public:
    B(int a = 0, int b = 0):A(a)
    {
        cout << "B()" << endl;
        this->b = b;
    }
    ~B()
    {
        cout << "~B()" << endl;
    }
    void show()
    {
        cout << "b = " << b << endl;
    }
};

// 基类C,虚继承
class C:virtual public A
{
private:
    int c;
public:
    C(int a = 0, int c = 0):A(a)
    {
        cout << "C()" << endl;
        this->c = c;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    void show()
    {
        cout << "c = " << c << endl;
    }
};

// 派生类,普通多继承
// B类和C类都虚继承于虚基类A,但是在Test类中只有一个虚基类A的成员变量
// 如果不是虚继承,就会有两个A类的成员变量,造成影子现象
class Test:public B, public C
{
private:
    int data;
public:
    // 这里采用构造函数列表初始化方式,但是后面的初始化并不能初始化虚基类成员变量,
    // 如果没有显式声明虚继类的构造函数,默认调用无参的构造函数
    // 可以改为如下
    // Test(int a = 0, int b = 0, int c = 0, int data = 0):A(a), B(a, b), C(a, c)
    Test(int a = 0, int b = 0, int c = 0, int data = 0):B(a, b), C(a, c)
    {
        cout << "Test()" << endl;
        this->data = data;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
    void show()
    {
        A::show();
        B::show();
        C::show();
        cout << "data = " << data << endl;
    }
};


int main()
{
    Test tmp(1, 2, 3, 4);
    tmp.show();


    return 0;
}

        3、显式调用虚基类的有参构造函数才能正确初始化虚基类成员 

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A
{
private:
    int a;
public:
    A(int a = 0)
    {
        cout << "A()" << endl;
        this->a = a;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void show()
    {
        cout << "a = " << a << endl;
    }
};

// 基类B,虚继承
class B:virtual public A
{
private:
    int b;
public:
    B(int a = 0, int b = 0):A(a)
    {
        cout << "B()" << endl;
        this->b = b;
    }
    ~B()
    {
        cout << "~B()" << endl;
    }
    void show()
    {
        cout << "b = " << b << endl;
    }
};

// 基类C,虚继承
class C:virtual public A
{
private:
    int c;
public:
    C(int a = 0, int c = 0):A(a)
    {
        cout << "C()" << endl;
        this->c = c;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    void show()
    {
        cout << "c = " << c << endl;
    }
};

// 派生类,普通多继承
// B类和C类都虚继承于虚基类A,但是在Test类中只有一个虚基类A的成员变量
// 如果不是虚继承,就会有两个A类的成员变量,造成影子现象
class Test:public B, public C
{
private:
    int data;
public:
    // 这里采用构造函数列表初始化方式,但是后面的初始化并不能初始化虚基类成员变量,
    // 如果没有显式声明虚继类的构造函数,默认调用无参的构造函数
    // 现在改为如下,显示调用
    Test(int a = 0, int b = 0, int c = 0, int data = 0):A(a), B(a, b), C(a, c)
    // Test(int a = 0, int b = 0, int c = 0, int data = 0):B(a, b), C(a, c)
    {
        cout << "Test()" << endl;
        this->data = data;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
    void show()
    {
        A::show();
        B::show();
        C::show();
        cout << "data = " << data << endl;
    }
};


int main()
{
    Test tmp(1, 2, 3, 4);
    tmp.show();


    return 0;
}

七、总结 

        虚继承的并不一定比多继承好,要具体情况具体分析,而且由于虚继承使用了虚指针和虚表,效率上比多继承的慢,当基类中出现多个重名的成员时,建议采用虚继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值