首页 > 编程笔记

C++虚继承的定义和使用

在程序设计过程中,通常希望间接基类的成员变量在底层派生类中只有一份拷贝,从而避免成员访问的二义性。

通过虚继承可以达到这样的目的,虚继承就是在派生类继承基类时,在权限控制符前加上 virtual 关键字,其格式如下所示:

class 派生类名:virtual 权限控制符 基类名
{
派生类成员
};

在上述格式中,在权限控制符前面添加了 virtual 关键字,就表明派生类虚继承了基类。

被虚继承的基类通常称为虚基类,虚基类只是针对虚继承,而不是针对基类本身。在普通继承中,该基类并不称为虚基类。

【示例1】下面通过案例让 Sofa 类和 Bed 类虚继承 Furniture 类,演示虚继承的作用,C++ 代码如下:
#include<iostream>
using namespace std;
class Furniture      //家具类Furniture
{
public:
    Furniture(string wood);     //Furniture类构造函数
protected:
    string _wood;       //成员变量_wood,表示材质
};
Furniture::Furniture(string wood)   //类外实现构造函数
{
    _wood=wood;
}
class Sofa:virtual public Furniture  //沙发类Sofa,虚继承Furniture类
{
public:
    Sofa(float length,string wood);  //Sofa类构造函数
protected:
    float _length;      //成员变量_length,表示沙发长度
};
//类外实现Sofa类构造函数
Sofa::Sofa(float length,string wood):Furniture(wood)
{
    _length=length;
};
class Bed:virtual public Furniture  //床类Bed,虚继承Furniture类
{
public:
    Bed(float width, string wood);  //Bed类构造函数
protected:
    float _width;       //成员变量_width,表示床的宽度
};
//类外实现Bed类构造函数
Bed::Bed(float width, string wood):Furniture(wood)
{
    _width=width;
}
class Sofabed:public Sofa,public Bed   //Sofabed类,公有继承Sofa类和Bed类
{
public:
    //构造函数
    Sofabed(float length,string wood1, float width,string wood2);
    void getSize();      //成员函数getSize(),获取沙发床大小
};
//类外实现Sofabed类构造函数
Sofabed::Sofabed(float length, string wood1, float width, string wood2):
    Sofa(length,wood1),Bed(width,wood2),Furniture(wood1)
{
}
void Sofabed::getSize()     //类外实现getSize()函数
{
    cout<<"沙发床长"<<_length<<"米"<<endl;
    cout<<"沙发床宽"<<_width<<"米"<<endl;
    cout<<"沙发床材质为"<<_wood<<endl;
}
int main()
{
    Sofabed sbed(1.8,"梨木",1.5,"檀木"); //创建Sofabed类对象sbed
    sbed.getSize();      //调用getSize()函数获取沙发床大小
    return 0;
}
运行结果:

沙发床长1.8米
沙发床宽1.5米
沙发床材质为梨木

示例分析:
在 Sofabed 类的 getSize() 函数中,第 54 行代码直接访问了 _wood 成员,但编译器并没有报错。这是因为在对象 sbed 中只有一个 _wood 成员数据。

在虚继承中,每个虚继承的派生类都会增加一个虚基类指针 vbptr,该指针位于派生类对象的顶部。

vbptr 指针指向一个虚基类表 vbtable(不占对象内存),虚基类表中记录了基类成员变量相对于 vbptr 指针的偏移量,根据偏移量就可以找到基类成员变量。

当虚基类的派生类被当作基类继承时,虚基类指针 vbptr 也会被继承,因此底层派生类对象中成员变量的排列方式与普通继承有所不同。例如,在【示例1】中,对象 sbed 的逻辑存储如图 1 所示。

图1 对象sbed的逻辑存储
图1 对象sbed的逻辑存储

上图说明:
另外,需要注意的是,在虚继承中,底层派生类的构造函数不仅负责调用直接基类的构造函数,还负责调用间接基类的构造函数。在整个对象的创建过程中,间接基类的构造函数只会调用一次。

优秀文章