纯虚函数
在函数后面写上=0 指定该函数为纯虚函数
含有一个或多个纯虚函数的类是抽象基类,不能创建对象。
class Base
{
public:
virtual void f() const =0;
virtual void fcn(int i)
{
cout<<i<<endl;
}
};
int main()
{
Base *b=new Base; //失败 纯虚类无法定义对象
getchar();
}
普通函数名称的特殊处理:
编译器会把重载的两个函数在编译阶段做优化,在名称上加入参数编码,制造出独一无二的效果。
如下所示:
void x(float newx);
float x();
处理为:
Void x_5pointx(floatnewx);
Void x_5pointy();
虚拟成员函数
ptr->pay()转化为:
(*ptr->vptr[1])(ptr);
实现模型,vptr与vtbl。Vtbl是在编译时,每个类(具有虚函数:继承或者自己声明的虚函数,或者纯虚函数)都会产生的一个虚函数表。每个类可能不止一个虚函数表,比如多重继承的情况下。【注意】一个类可能具有多个虚函数表。
虚函数表中的内容包括该类自己具有的虚函数,从其他函数继承来的虚函数,纯虚函数实体,同时为了支持虚继承,某些实现会把虚基类地址也放到vtbl里。
Vtbl则是每个对象都拥有的一个指向虚函数表的指针。在对象产生时的构造函数里设置,由编译器生成设置代码。一般而言我们不知道ptr所指对象的真正类型,但是经过ptr总是可以访问对象的虚表。虽然不知道那个z()函数实体被调用,但是z()地址总是会放到相同的slot中。于是一个多态的访问Ptr->z();总是可以被转换成(*Ptr->vptr[4])(ptr),这里vptr表示编译器安插的指针,4表示z()放置的slot编号。
多重继承下的虚拟成员函数
深入浅出C++虚函数表
class A { public: virtual int add(int i); };虚函数表
如果类A的定义如下: class A { public: int i; virtual int add(int i); } 那么sizeof(A) == 8。所以类的大小等于vtable的size加上数据成员的size,如果他有父类,则还需要加上父类中数据成员的size。例如: class B: public A { public: int k; } cout<<sizeof(B); 输出12。 B的vtable (4bytes)+ B::k (4bytes) + A::i (4bytes) =12 bytes。 多态的原理 struct Super { int data; virtual int add(int i){return i;}; virtual string toString()=0; }; class Sub: public Super { public: string toString() { return string("Sub class"); } }; Super* s = new Sub(); cout<<s->toString()<<endl; delete s; 输出”Sub class”。 Super vtable
第一, 虚函数表在类所有成员的最前面。 第二, 第二,Sub因为重写了toString,Sub的虚函数表就记录者Sub的toString的地址。 上面的程序Super* s = new Sub(); s实际指向的是Sub类对象的内存区域,所以调用方法的时候会根据这片内存记录的函数地址进行调用,多态就是这么实现的。 访问虚函数表,手动调用虚函数 首先来看一项技术 struct S { int k; int m; }; S a; int i = 5; a.k = 5; a.m = 4; cout<<*(int*)&a<<endl; 输出5。 说明一下这段程序,我想就是最一行语句理解起来不太容易,把它拆开来看看会很清晰。 首先用int*型指向S类型 int* pi = (int*)&a; 然后用解引用来查看这片内存存储的int型数据,合起来就*(int*)&a是这种形式。 如此咱们就有了方法访问一个类的任意一片内存了,那么就先找到虚函数表的地址吧! 了解一个数据类型DWORD_PTR,它被定义为32位的指针(64位系统下64位),我们就用这个指针来找到虚函数表的地址。 DWORD_PTR pvTable; pvTable = (DWORD_PTR)&Sub(); Sub()创建了一个Sub的临时对象,&Sub()表示取这个对象的地址,然后把它强制类型转换为DWORD_PTR,上文说过虚表是在类的最上面,所以pvTable就指向了这个类的虚函数表。 如下代码访问此类的虚函数: ((Sub*)pvTable)->toString() 利用这种技术可以实现动态调用,具体实现不说明了,可以参考<<精通MFC>>P19,基本思想是实现一个与被调用类匹配的结构,然后进行类型转换就可以映射到相应位置的内存。 struct CompitibleStruct { DWORD_PTR vtable; int data; }; 其他 struct ISwitch { virtual void On() = 0; virtual void Off() = 0; }; 定义一个ISwitch接口。参考java或者c#的语言规定,一个接口不能从类继承,所以这个接口的虚函数表其实没有任何意义,Microsoft为这种情况发明了一个修饰符__declspec(novtable)。为纯虚类加上这个关键字不错的选择,可以减少编译出来的冗余信息,这个技术被用到__interface关键字上了,ATL中能看到。
|