快捷搜索:   服务器  安全  linux 安全  MYSQL  dedecms

C++基础:C++中的虚函数的实现方法

    学习 C++ 的同志不知道有没有和我一样遇到过这样的困惑:C++中的虚函数到底怎么实现的?在各种继承关系中,虚函数表的结构到底是什么样的?曾经我是很想当然,可是后来在使用ATL的过程中,我发现并不是我想的那样。大家知道,利用C++语言本身的特性进行COM编程当然是很方便的事,但是你就得随时随地都知道那虚函数表里头到底是些什么东西。讲C++语法的书没有义务告诉你C++产生的虚函数表是什么样的,这就是头痛的所在。
 
    自已做试验是件很快乐的事,我很愿意这么做。

    首先写个函数,作为我们实验的基础。传入虚函数表指针,显示虚数表的内容。

 void DispVFT(DWORD* pVFT)
{
 
 printf(\"VFT Pointer:%p\\n\" , pVFT);
 printf(\"Begin\\n\");
 DWORD* p = (DWORD* )*pVFT;//得到VFT的首址
 while(*p) //这个地方我是看表项是不是为空来判断是否到了表尾,
    //大多数情况都是对的,不过不能为准
 {
  printf(\"VF:%p , %p\\n\", p , *p);
  p++;
 }
 printf(\"End\\n\\n\");
}


    首先我们看单个类时的虚函数表的情况:

 class C1
{
public:
 C1()
 {
  //printf(\"In/ C1\\n\");
  //DispVFT((DWORD*)this/);
 };
 virtual F1()
 {}
};
void main()
{
 C1 c1;

 //由于C1中没有成员数据,所有我们可以用这种方式判断
 //C1/中的虚函数表指针的个数
 printf(\"vftptr count :%d\\n\" , sizeof(C1) / 4);
 //显示内存结构
 DispVFT((DWORD* )&c1);
}


    输出:

    vftptr count :1

    VFT Pointer:0012FF7C

    Begin

    VF:00420048 , 00401078(C1::F1)

    End

    很单纯,不用多讲,这是我们意料之中的结果。


    下面我们进行简单继承的实验

 

class C2 : public C1
{
public:
 C2(){
  printf(\"In C1\\n\");
  DispVFT((DWORD*)this);
 }
 virtual F2()
 {}
};

void main()
{
 C2 c2;
 C1* pC1 = &c2;
 printf(\"vftptr count :%d\\n\" , sizeof(C2) / 4);
 printf(\"C1\\n\");
 DispVFT((DWORD*)pC1);
 printf(\"C2\\n\");
 DispVFT((DWORD*)&c2);
}

    输出:

    In C1

    VFT Pointer:0012FF7C

    Begin

    VF:00420048 , 00401087(C1::F1)

    End

    In C2

    VFT Pointer:0012FF7C

    Begin

    VF:004200F4 , 00401087(C1::F1) //输出的第一项是表的首址与对应的表项内容,看看地址,与 In C1的不同,说明是不同的两个表

    VF:004200F8 , 0040108C(C2::F2)

    End

    vftptr count :1

    C1

    VFT Pointer:0012FF7C

    Begin

    VF:004200F4 , 00401087(C1::F1)

    VF:004200F8 , 0040108C(C2::F2)

    End

    C2

    VFT Pointer:0012FF7C

    Begin

    VF:004200F4 , 00401087(C1::F1)

    VF:004200F8 , 0040108C(C2::F2)

    End

    大家可以看到最后虚函数表指针仍然是同一个,表中按顺序放入了C1(基类)与C2(派生类)的虚函数指针。

    下面是多重继承

 class C1
{
public:
 C1()
 {
  //printf(\"In/ C1\\n\");
  //DispVFT((DWORD*)this/);
 }
 virtual F1(){}
};

class C2
{
public:
 C2(){
  //printf(\"In/ C1\\n\");
  //DispVFT((DWORD*)this/);
 }
 virtual F2(){}
};
class C3 : public C1 , public C2
{
public:
 C3(){
  //printf(\"In/ C1\\n\");
  //DispVFT((DWORD*)this/);
 }
 virtual F3(){}
};

void main()
{
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf(\"vftptr count :%d\\n\" , sizeof(C3) / 4);
 printf(\"C1\\n\");
 DispVFT((DWORD*)pC1);
 printf(\"C2\\n\");[Page]
 DispVFT((DWORD*)pC2);
 printf(\"C3\\n\");
 DispVFT((DWORD*)&c3);
}

    输出:

    vftptr count :2

    C1

    VFT Pointer:0012FF78

    Begin

    VF:00420104 , 00401046(C1::F1)

    VF:00420108 , 0040101E(C3::F3)

    End

    C2

    VFT Pointer:0012FF7C

    Begin

    VF:00420100 , 00401028(C2::F2)

    VF:00420104 , 00401046(C1::F1)

    VF:00420108 , 0040101E(C3::F3)

    End

    C3

    VFT Pointer:0012FF78

    Begin

    VF:00420104 , 00401046(C1::F1)

    VF:00420108 , 0040101E(C3::F3)

    End

    虚函数表指针变成两个了,也就是说现在是用两个虚函数表指针维护一个表,总结一下就是,虚函数表指针的个数等于基类的个数。至于虚函数表的个数,应该是三个。你可以把我在构造函数中加的代码去掉注释符,看看输出。你会发现每次输出的表的首地址都是不一样,那表当然也不是同一个表。



    下面说说多层继承的情况:

 class C1
{
public:
 C1()
 {
  printf(\"In C1\\n\");
  DispVFT((DWORD*)this);
 }
 virtual F1(){}
};

class C2 : public C1
{
public:
 C2(){
  printf(\"In C2\\n\");
  DispVFT((DWORD*)this);
 }
 virtual F2(){}
};
class C3 : public C2
{
public:
 C3(){
  printf(\"In C3\\n\");
  DispVFT((DWORD*)this);
 }
 virtual F3(){}
};


 void main()
{
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf(\"vftptr count :%d\\n\" , sizeof(C3) / 4);
 printf(\"C1\\n\");
 DispVFT((DWORD*)pC1);
 printf(\"C2\\n\");
 DispVFT((DWORD*)pC2);
 printf(\"C3\\n\");
 DispVFT((DWORD*)&c3);
 
}

   
    输出:

 In C1
VFT Pointer:0012FF7C
Begin
VF:00421090 , 00401046(C1::F1)
End

In C2
VFT Pointer:0012FF7C
Begin
VF:0042010C , 00401046(C1::F1) //这里是类C2的vftable,第一个输出是它首址与表项内容
VF:00420110 , 00401028(C2::F2)
End

In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046(C1::F1)
VF:004210A0 , 00401028(C2::F2)
VF:004210A4 , 00401078(C3::F3)
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End


    得到的结果:我们看到了虚函数表指针是一个,可是你仔细看看每个构造函数的输出!输出的第一项是表的首址与对应的表项。大家可以看到,首址都是不一样的这说明是三个不同的表,那么这个类就有三个虚函数表。你可能会想,这三个表在什么时候用呢,事实上,在C3的实例被构造出来后,只有最后一个表,也就是C3的表在用,其它的表跟本就是没有用的,C++在没有通过你同意的情况下,在浪费你的空间(多重继承也存在同样的问题)。微软想了个办法把其它的不用的虚函数表去掉:__declspec(novtable)

 class __declspec(novtable)C1
{
public:
 C1()
 {
  //printf(\"In/ C1\\n\");[Page]
  //DispVFT((DWORD*)this/); //这里得去掉,既然没有那个表,怎么输出
 }
 virtual F1(){}
};

class __declspec(novtable)C2 : public C1
{
public:
 C2(){
  //printf(\"In/ C2\\n\");
  //DispVFT((DWORD*)this);//这里得去掉,既然没有那个表,怎么输出
 }
 virtual F2(){}
};
class C3 : public C2
{
public:
 C3(){
  printf(\"In C3\\n\");
  DispVFT((DWORD*)this);
 }
 virtual F3(){}
};


 void main()
{
 
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf(\"vftptr count :%d\\n\" , sizeof(C3) / 4);
 printf(\"C1\\n\");
 DispVFT((DWORD*)pC1);
 printf(\"C2\\n\");
 DispVFT((DWORD*)pC2);
 printf(\"C3\\n\");
 DispVFT((DWORD*)&c3);
 
}

    输出:

 In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End


    可以看到一切正常,只是在不知不觉中,你的程序瘦身成功,不过如果你决定要去掉类的虚函数表,你最好可以确定这个类应该是个被继承的基类,而不是最后派生使用的类。否则可能会出错,比如:

 void func1(C1* p)

{

 p->F1();

}

void main()

{

 C2 c2;

 func1(&c2);

}

顶(0)
踩(0)

您可能还会对下面的文章感兴趣:

最新评论