C++虚继承的定义和使用
在程序设计过程中,通常希望间接基类的成员变量在底层派生类中只有一份拷贝,从而避免成员访问的二义性。
通过虚继承可以达到这样的目的,虚继承就是在派生类继承基类时,在权限控制符前加上 virtual 关键字,其格式如下所示:
被虚继承的基类通常称为虚基类,虚基类只是针对虚继承,而不是针对基类本身。在普通继承中,该基类并不称为虚基类。
【示例1】下面通过案例让 Sofa 类和 Bed 类虚继承 Furniture 类,演示虚继承的作用,C++ 代码如下:
在 Sofabed 类的 getSize() 函数中,第 54 行代码直接访问了 _wood 成员,但编译器并没有报错。这是因为在对象 sbed 中只有一个 _wood 成员数据。
在虚继承中,每个虚继承的派生类都会增加一个虚基类指针 vbptr,该指针位于派生类对象的顶部。
vbptr 指针指向一个虚基类表 vbtable(不占对象内存),虚基类表中记录了基类成员变量相对于 vbptr 指针的偏移量,根据偏移量就可以找到基类成员变量。
当虚基类的派生类被当作基类继承时,虚基类指针 vbptr 也会被继承,因此底层派生类对象中成员变量的排列方式与普通继承有所不同。例如,在【示例1】中,对象 sbed 的逻辑存储如图 1 所示。

图1 对象sbed的逻辑存储
上图说明:
另外,需要注意的是,在虚继承中,底层派生类的构造函数不仅负责调用直接基类的构造函数,还负责调用间接基类的构造函数。在整个对象的创建过程中,间接基类的构造函数只会调用一次。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
通过虚继承可以达到这样的目的,虚继承就是在派生类继承基类时,在权限控制符前加上 virtual 关键字,其格式如下所示:
class 派生类名: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米
沙发床材质为梨木
- 第 14~20 行代码定义了沙发类 Sofa,Sofa 类虚继承 Furniture 类;
- 第 26~32 行代码定义床类 Bed,Bed 类虚继承 Furniture 类;
- 第 38~44 行代码定义沙发床类 Sofabed,Sofabed 公有继承 Sofa 类和 Bed 类;
- 第 58~59 行代码,创建Sofabed类对象 sbed,并通过对象 sbed 调用 getSize() 函数获取沙发床大小。
在 Sofabed 类的 getSize() 函数中,第 54 行代码直接访问了 _wood 成员,但编译器并没有报错。这是因为在对象 sbed 中只有一个 _wood 成员数据。
在虚继承中,每个虚继承的派生类都会增加一个虚基类指针 vbptr,该指针位于派生类对象的顶部。
vbptr 指针指向一个虚基类表 vbtable(不占对象内存),虚基类表中记录了基类成员变量相对于 vbptr 指针的偏移量,根据偏移量就可以找到基类成员变量。
当虚基类的派生类被当作基类继承时,虚基类指针 vbptr 也会被继承,因此底层派生类对象中成员变量的排列方式与普通继承有所不同。例如,在【示例1】中,对象 sbed 的逻辑存储如图 1 所示。

图1 对象sbed的逻辑存储
上图说明:
- 对象 sbed 顶部是基类 Sofa 的虚基类指针和成员变量;紧接着是基类 Bed 的虚基类指针和成员变量。
- 间接基类 Furniture 的成员变量在对象 sbed 中只有一份拷贝,放在最下面。
- Sofa 类的虚基类指针 Sofa::vbptr 指向了 Sofa 类的虚基类表,该虚基类表中记录了 _wood 与 Sofa::vbptr 的距离,为 16 字节。
- Bed 类虚基类表记录了 _wood 与 Bed::vbptr 的距离,为 8 字节。通过偏移量就可以快速找到基类的成员变量。
另外,需要注意的是,在虚继承中,底层派生类的构造函数不仅负责调用直接基类的构造函数,还负责调用间接基类的构造函数。在整个对象的创建过程中,间接基类的构造函数只会调用一次。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。