一、什么是虚继承
虚继承是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; }
七、总结
虚继承的并不一定比多继承好,要具体情况具体分析,而且由于虚继承使用了虚指针和虚表,效率上比多继承的慢,当基类中出现多个重名的成员时,建议采用虚继承。