C++虚函数详解

纯虚函数

在函数后面写上=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中的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

Super:: add地址

Super::toString地址

Sub vtable

Super::add地址

Sub::toString地址

int data;


编译器会把虚表编译成上面的样式,注意两件事情:

第一,   虚函数表在类所有成员的最前面。

第二,   第二,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;

};

其他
C++中没有接口的概念,一般要实现接口都用pure virtual class(纯虚类),下面代码举一个实例:

struct ISwitch

{

       virtual void On() = 0;

       virtual void Off() = 0;

};

定义一个ISwitch接口。参考java或者c#的语言规定,一个接口不能从类继承,所以这个接口的虚函数表其实没有任何意义,Microsoft为这种情况发明了一个修饰符__declspec(novtable)。为纯虚类加上这个关键字不错的选择,可以减少编译出来的冗余信息,这个技术被用到__interface关键字上了,ATL中能看到。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值