virtual的三种用法,引出C++类的模型
virtual用途:
1.虚成员函数
2.虚析构函数 (用于基类)//有个疑问,不如所有的析构函数都写为虚函数。
3.虚继承 (用于菱形继承的情况)
以下模型内容,部分转自:https://www.cnblogs.com/raichen/p/5744300.html
C++ 对象模型:单继承、多继承、虚继承
数据成员分为静态和非静态,成员函数有静态非静态以及虚函数
C++对象模型:
在此模型下,nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。
static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持。
class Base
{
public:
Base(int i) :baseI(i){};
int getI(){ return baseI; }
static void countI(){};
virtual void print(void){ cout << "Base::print()"; }
virtual ~Base(){}
private:
int baseI;
static int baseS;
};
1.每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列。
2.每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端(也就是说对象的地址就是vptr的地址)。
3.虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。
单继承(父类含虚函数)
对普通单继承而言:
子类与父类拥有各自的一个虚函数表
若子类并无overwrite父类虚函数,用父类虚函数
若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数
若子声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后
#include <iostream>
using namespace std;
class Base
{
public:
virtual void fun1(){ cout << "Base fun1" << endl; }
virtual void fun2(){ cout << "Base fun2" << endl; }
private:
int a;
};
class Derive : public Base
{
public:
void fun2(){ cout << "Derive fun2" << endl; }
virtual void fun3(){}
private:
int b;
};
int main()
{
Base b;
Derive d;
Base *p = &d;
p->fun1(); // Base fun1
p->fun2(); //Derive fun2
system("pause");
return 0;
}
一般多继承(不考虑菱形继承)
若子类新增虚函数,放在声明的第一个父类的虚函数表中
若子类重写了父类的虚函数,所有父类的虚函数表都要改变:如fun1
内存布局中,父类按照其声明顺序排列
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void fun1(){}
virtual void fun2(){}
virtual void fun3(){}
virtual void fun4(){}
virtual void fun5(){}
virtual void fun6(){}
private:
};
class Base2
{
public:
virtual void fun1(){}
virtual void fun2(){}
private:
int m_base2;
};
class Derive : public Base1,public Base2
{
public:
void fun1(){}
virtual void fun3(){}
private:
int m_derive;
};
int main()
{
Base1 b1;
Base2 b2;
Derive d;
cout <<"b1:" <<sizeof(b1) << endl; // 4
cout << "b2:" << sizeof(b2) << endl; // 8
cout <<"d:" << sizeof(d) << endl; //16
system("pause");
return 0;
}
虚继承
虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况。
1.虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个新的虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面(对比非虚继承:直接扩展父类虚函数表)
2.虚继承的子类也单独保留了父类的vprt与虚函数表
3.虚继承的子类有虚基类表指针(vbptr)