关于vptr(虚函数表指针)和btbl(虚函数表)
最右边的是A、B、C三个class的继承关系,左边的是A,B,C三个类的三个对象a,b,c的对象模型(表示在内存中的结构);
在对象模型中,会有虚函数表指针和数据,虚函数表指针是内存头,即对象的地址也是虚函数表的地址;
虚函数表指针vptr是指向虚函数表vtbl的,vtbl内会有类里面所有虚函数的地址,并且是按在类里面声明的顺序,按数组方式排列(表头是0);
如果有一个指针p指向对象c,那么我们就可以通过这个指针和地址,直接调用对象内的虚函数(而不用通过函数名),上面的便有它的调用方法(通过指针p,找到虚表指针vptr,再找出它的虚表vtbl取出其中的第n个,把它当函数指针,去调用p,因为p是调用者所以this就是指向p)
当一个函数调用,同时满足下面的3个条件时,(1)对象通过指针被调用的,(2)对象是向上转型的(它定义的是自己的类型,但其实是父类的类型,这就叫做向上转型),(3)调用的是对象的虚函数,编译器就会把这个调用动作编译为上面所说的调用方式,称为动态绑定(相对的是,不通过指针调用的静态绑定,)。
关于this
在调用流程图中,子类对象调用了父类的函数,调用者是子类对象,所以this是指向类子对象;
这样,被调用的函数里面的每一个函数都会写成通过this指针调用的形式,上面便是;
接着如果被调用的函数是虚函数,就会被写成动态绑定,上面便是;
这就是之前所说的,满足三个条件的函数调用,会被编译器写成动态绑定形式。
关于Dynamic Binding
静态绑定,在编译成汇编时,会有直接的地址给出;
而通过指针的动态绑定,则没有。
关于const
成员函数的名称后面,可以加上const,表示它不打算改变class的data。
之前有提及成员函数不加const的,可能使数据发生改变;也提及过加与不加const的成员函数的签名是不一样的;解释如下:
const object是不能调用non-const
member functions的,前者说不能改变,但后者则可能改变,后者不能包容前者。
而且,当成员函数的const和non-const版本同时存在,const object只能调用const版本,non-const object也只会调用non-const版本。
如果当初设计String中的print()时未指明const,那么上行便是经由const object调用non-const member function,这不符合上面所以的规则显然是会出错的。
(对于加不加const,其实在函数起名的时候就已经可以确定了,因为名称代表着它的动作,从动作就可以知道是不是要加const)
关于new和delete
如何重载new和delete?
new和delete是表达式,表达式的行为是不能变的;
之前我们学过,new和delete是会被分解多个步骤的;
据此,我们可以通过重载它分解步骤里所调用的函数,实现对new和delete的重载。
上面便是重载的接口。
operator new( new [ ] )和operator delete( delete [ ] )的重载
operator new和operator delete的实现:
operator new[]和operator delete[]的实现:
为什么要加4?下面有解释。
调用示例
在示例中上面的是无虚函数时的情况,下面的是有虚函数时的情况。
我们已经把重载写好,重载里面还会用cout打印出调用数据。
调用测试,分别用1,2,3,4行进行;
第1,2行的调用数据显示,有虚函数的比无虚函数的多4,因为多了虚函数表指针(在32位环境下,指针占4);
第3,4行,动态分配array时,会顺序调用5次构造函数,array
delete则是逆顺序调用析构函数;它在数据包之前会有一块大小为4的内存块,作为计数器counter,记录数据的个数(这里counter的是5,表示有5个元素,这能使编译器很快知道要调用5次构造和析构函数),所以它的大小是每个object的大小之和,加上4(注,每个object都会有自己的虚函数表指针,不要理解错了)
如果调用时采用下行的方式,那么将不会进入刚才重载的函数,而是调用原有的全局函数。
关于class member operator new()的更多重载:
之前我们已经重载成员函数的operator new()(成员函数的调用者一定是这种class的对象),如果我们要重载更多的版本,那么要在它原来的基础上增加参数列(原来的只有一个size_t类型的形参),并且它的第一个参数必须是size_t类型(代表某块内存的大小的类型,size type),每个版本的重载也必须有不同的参数列,上面便是声明和调用形式,调用时要在new()的小括号里写上除了第一参数之外的其余参数,这些小括号里额外的参数叫做placement
new。
我们也可以重载与上面有额外参数的相对应的class member operator
delete(),但它并不能被delete语句调用,它只有在当对应的operator new()所调用的ctor抛出异常时,才会被调用,去delete未完成创建的内存块。