虚基类:虚基类的使用和为了实现多态而使用的虚函数不同,是为了解决多重继承的二义性问题。
首先贴上一段代码实例:
#include <stdio.h> #include <stdlib.h> #include <iostream> #include <math.h> #include <vector> class A { public: int a; void fun(){ std::cout << "A" << std::endl; } }; class B :virtual public A { public: int b; }; class C :virtual public A { public: int c; }; class D : public B,public C { public: int d; }; int main() { D d; //d.fun(); //d.B::fun(); std::cout << sizeof(A) << std::endl; std::cout << sizeof(B) << std::endl; std::cout << sizeof(C) << std::endl; std::cout << sizeof(D) << std::endl; getchar(); return 0; }
如代码所示,该继承是一种菱形继承,如果不实用虚继承(virtual),则每个A的派生类中都会有一个int a的拷贝,这会在D的使用中产生二义性,而使用了虚继承之后,D中就只有一份int a 的拷贝,该拷贝不来自B也不来自C,而是直接来自于A的一份拷贝。
首先我们先来验证一下虚基类和派生类的内存大致分布。
测试环境:Windows 7 64bit+vs2013+32位程序
为什么A B C D的大小分别为4、12、12、24?
因为A中只有一个成员变量,int a,所以占4个字节(空类占一个字节);
B虚继承A,有一个指向A的指针(虚函数表指针),占4个字节,加上int b成员变量,再加上A中的int a,共12个字节;
C和B一样,12个字节;
D中首先保存B和C指向A的指针(B和C不一样),8个字节,加上int d成员变量,再加上继承来的int b,int c,最后一份来自A的拷贝,一共24个字节。
此时如果声明一个D d; d.a;没有任何问题,不会产生二义性。
但是:在访问a的时候通过两层,B的虚指针和A找到int a,隐形降低了效率,比非虚继承要效率低一点。
关于虚函数和虚继承首先来看一个实例
1 #include <iostream> 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<assert.h> 5 using namespace std; 6 class A 7 { 8 public: 9 A(){ cout << "A构造函数" << endl; } 10 ~A(){ cout << "A析构函数" << endl; } 11 virtual void print(){ cout << "父类的虚函数" << endl; } 12 void fun(){ cout << "fun a" << endl; } 13 virtual void V_fun(){ cout << "virtual fun a" << endl; } 14 }; 15 class B :virtual public A 16 { 17 public: 18 B(){ cout << "B构造函数" << endl;} 19 ~B(){ cout << "B析构函数" << endl;} 20 void fun(){ cout << "fun b" << endl; } 21 virtual void print(){ cout << "B派生类实现父类的纯虚函数" << endl; } 22 virtual void V_fun(){ cout << "virtual fun b" << endl; } 23 }; 24 class C:virtual public A 25 { 26 public: 27 C(){ cout << "C构造函数" << endl; } 28 ~C(){ cout << "C析构函数" << endl; } 29 void fun(){ cout << "fun c" << endl; } 30 //virtual void print(){ cout << "C派生类实现父类的纯虚函数" << endl; } 31 }; 32 class D :public B,public C 33 { 34 public: 35 D(){ cout << "D构造函数" << endl; } 36 ~D(){ cout << "D析构函数" << endl; } 37 }; 38 int main() 39 { 40 A *a = new B(); 41 a->fun(); 42 a->V_fun(); 43 a->print(); 44 getchar(); 45 return 0; 46 }
1 D d; 2 d.print(); 3 d.B::print(); 4 d.C::print();
多重继承情况下,如果是虚继承,以上几种访问方式都正确,反之只有后面两种方式可以。
虚函数:
函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字“virtual”声明为虚函数。而虚函数是实现多态的重要手段,意思是只有对虚函数的调用才能动态决定调用哪一个函数,这是相对于普通成员函数而言的,普通的成员函数在编译阶段就能确定调用哪一个函数。
基类A有两个成员函数fn和v_fn,派生类B继承自基类A,同样实现了两个函数,然后在main函数中用A的指针指向B的实例(向上转型,也是实现多态的必要手段),然后分别调用fn和v_fn函数。结果是“fn in A"和"virtual fn in B"。这是因为fn是普通成员函数,它是通过类A的指针调用的,所以在编译的时候就确定了调用A的fn函数。而v_fn是虚函数,编译时不能确定,而是在运行时再通过一些机制来调用指针所指向的实例(B的实例)中的v_fn函数。假如派生类B中没有实现(完全一样,不是重载)v_fn这个函数,那么依然会调用基类类A中的v_fn;如果它实现了,就可以说派生类B覆盖了基类A中的v_fn这个虚函数。这就是虚函数的表现和使用,只有通过虚函数,才能实现面向对象语言中的多态性。
虚继承:
为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样不仅就解决了二义性问题,也节省了内存,避免了数据不一致的问题。
class 派生类名:virtual 继承方式 基类名
virtual是关键字,声明该基类为派生类的虚基类。
在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。
声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。
语法:
class 派生类: virtual 基类1,virtual 基类2,...,virtual 基类n
{
...//派生类成员声明
};
执行顺序
首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;
执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;
执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;
执行派生类自己的构造函数;
析构以与构造相反的顺序执行;
解决了二义性问题,也节省了内存,避免了数据不一致的问题。
C++虚函数解析:看陈大神的专栏吧http://blog.csdn.net/haoel/article/details/1948051/