C++对象模型目录
C++ - 对象模型之 编译器何时才会自行添加构造函数
C++ - 对象模型之 内存布局
C++ - 对象模型之 成员函数调用
C++ - 对象模型之 构造和析构函数都干了什么
C++ - 对象模型之 类对象在执行时是如何生成的
C++ - 对象模型之 模板、异常、RTTI的实现
C++ - 对象模型之 成员函数调用
C++ 类的函数共有三种,
nonestatic member function:如果调用该函数,必须实例化一个对象,然后通过对象调用,那么调用的函数处理的就是该对象。
static member function:该函数不属于任何一个对象,即使没有对象也能够调用该函数。virtual member function:多态,根据继承关系来确定对象具体调用哪个类的函数。
该类型函数和nonestatic member function函数区别在于它的参数不会带有this指针,它和普通的C函数一样,可以直接调用。由于这个特点,所以它有以下特点和用途:
1. 不能直接调用和处理nonestatic member data,因为data属于具体的某个对象,static函数无法获得对象的指针,所以无法获得该对象的data;
2. 不能使const、virtual,因为const是保护member data,既然static函数无法直接调用member data,所以没有必要声明为const,另外不管怎么继承,static函数无法获得继承后对象,所以更谈不上virtual;
3. 由于和C的函数基本一样,所以可以作为回调函数;
对于单一继承、多重继承、虚拟继承下的virtual function调用是有区别的,所以,我们分类说明,在说明这些问题的时候,要和《C++ - 对象模型之 内存布局》的案例对照说明。
如《C++ - 对象模型之 内存布局》的“单一继承&多态”部分的代码,里面三个类GrandFather、Father、Child的vtbl布局如下
GrandFather:
|GrandFather::who()|
|GrandFather::fishing()|
|GrandFather::hungry()|
Father:
|Father::who()|
|GrandFather::fishing()|
|Father::hungry()|
|Father::cutting()|
Child:
|Child::who()|
|GrandFather::fishing()|
|Child::hungry()|
|Father::cutting()|
|Child::studying()|
1. 一个class不管有多少实例,这些实例可以共用一个vtbl,因为同类型的class的vtbl布局是一样的;
2. 为了在编译器,就能够确定virtual function对应的slot索引值,所以virtual function的排列顺序是先遍历base class,然后再遍历derived class,并且,一个virtual function不会被索引两次;
3. 每个class 实例有自己的vptr,该vptr指向vtbl;
如《C++ - 对象模型之 内存布局》的“多重继承&多态”部分的代码,里面五个类GrandFather、Father、Grandad、Mother、Child的vtbl布局如下
GrandFather:
|GrandFather::who()|
|GrandFather::fishing()|
|GrandFather::hungry()|
Father:
|Father::who()|
|GrandFather::fishing()|
|Father::hungry()|
|Father::cutting()|
Grandad:
|Grandad::who()|
|GrandFather::chessing()|
|GrandFather::hungry()|
Mother:
|Mother::who()|
|GrandFather::chessing()|
|Mother::hungry()|
|Mother::sewing()|
Child:
vptr1:
|Child::who()|
|GrandFather::fishing()|
|Child::hungry()|
|Father::cutting()|
|Child::studying()|
vptr2:
|Child::who()|
|GrandFather::chessing()|
|Child::hungry()|
|Mother::sewing()|
我们得到如下的结论:1. Child有两个vptr,一个指向第一个base,一个指向第二个base,有多少base,就有多少vptrs;
2. Child的studying()函数只在第一个vptr中有索引,这样可以节省空间,这也就引出了第一个base是主要实体,后继的base都是次要实体;
3. 如果用Child指针或Father指针或GrandFather指针处理Child,包括析构,都没有区别,因为这个时候指针真正指向的Child Object的起始地址;
4. 如果用Mother指针或者Grandad指针处理Child,就必须调整this指针了。
如Mother * m = &child;
m的地址其实是&child+sizeof(Father),一般的处理也不会有什么大的问题,但是当出现delete m时,必须将调整m地址指向child的起始地址,但是这个时候,如何才能知道m实际指向的是Mother实例还是Child实例呢。