`
helpbs
  • 浏览: 1161575 次
文章分类
社区版块
存档分类
最新评论

ATL布幔之下的秘密(2)

 
阅读更多

介绍
  在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术,这是本系列的第二篇文章。

  现在让我们来探究一些虚函数背后更加有趣的资料。为了和上文保持一致,在本文的讨论中我将使用相同的顺序,程序的序号从20开始。
  让我们看看下面这个程序:
程序20.
#include<iostream>
usingnamespacestd;

classBase{
public:
virtualvoidfun(){
cout<<"Base::fun"<<endl;
}
voidshow(){
fun();
}
};

classDrive:publicBase{
public:
virtualvoidfun(){
cout<<"Drive::fun"<<endl;
}
};

intmain(){
Drived;
d.show();

return0;
}
  程序的输出为:
Drive::fun

  这个程序清楚地示范了基类的函数是如何调用派生类的虚函数的。这一技术被用于不同的框架中,例如MFC和设计模式(比如TemplateDesignPattern)。现在你可以修改一下这个程序来看看它的行为,我将要在基类的构造函数中调用虚函数,而不是普通的成员函数。
程序21.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
fun();
}
virtualvoidfun(){
cout<<"Base::fun"<<endl;
}
};

classDrive:publicBase{
public:
virtualvoidfun(){
cout<<"Drive::fun"<<endl;
}
};

intmain(){
Drived;

return0;
}
  程序的输出为:
Base::fun
  这个程序表明,我们不能在基类的构造函数中调用派生类的虚函数。好了,那就让我们来看看着布幔之下到底做了什么。我将会把这些构造函数之中的指针值打印出来,为了简便起见,我移除了类中其它的函数。
程序22.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"ThisPointer="<<(int*)this<<endl;
cout<<endl;
}
virtualvoidf(){cout<<"Base::f"<<endl;}
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"ThisPointer="<<(int*)this<<endl;
cout<<endl;
}
virtualvoidf(){cout<<"Drive::f"<<endl;}
};

intmain(){
Drived;
cout<<"InMain"<<endl;
cout<<(int*)&d<<endl;

return0;
}
  程序的输出为:
InBase
ThisPointer=0012FF7C

InDrive
ThisPointer=0012FF7C

InMain
0012FF7C

  这就表示,整个内存位置中,只有一个对象的存在。那么就让我们把这个指针指向的值打印出来,也就是虚函数表的指针vptr指向的值,VTable的地址。
程序23.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Base::f1"<<endl;}
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f2"<<endl;}
};

intmain(){
Drived;
return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C08C
ValueatVtable=004010F0

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C07C
ValueatVtable=00401217

  这个程序示范了基类和派生类中不同的虚函数表地址。为了更好地弄懂这一问题,那就让我们把继承层次加深,并添加一个继承于Drive类的MostDrive类,然后构建一个它的对象。
程序24.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Base::f1"<<endl;}
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f2"<<endl;}
};

classMostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;
return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0A0
ValueatVtable=004010F5

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C090
ValueatVtable=00401221

InMostDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C080
ValueatVtable=00401186

  这个程序示范了虚函数表指针在每个类的构造函数中的初始化过程。这样看来,每个类构造函数中虚函数表的地址是不同的,main函数则使用了继承链中的最底部的派生类来创建对象。
  现在你可以看看虚函数表中各个类的构造函数的位置了,你可以将虚函数表中的第一个入口存入一个函数指针,并尝试运行它。
程序25.
#include<iostream>
usingnamespacestd;

typedefvoid(*Fun)();

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;

FunpFun=(Fun)*(int*)*(int*)this;
pFun();
cout<<endl;
}
virtualvoidf1(){cout<<"Base::f1"<<endl;}
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;

FunpFun=(Fun)*(int*)*(int*)this;
pFun();
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f1"<<endl;}
};

classMostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable="<<(int*)*(int*)*(int*)this<<endl;

FunpFun=(Fun)*(int*)*(int*)this;
pFun();
cout<<endl;
}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
};

intmain(){
MostDrived;
return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C098
ValueatVtable=004010F5
Base::f1

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C088
ValueatVtable=00401221
Drive::f1

InMostDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C078
ValueatVtable=00401186
MostDrive::f1

  这个程序示范了每个类中的构造函数是如何用自己的虚函数来填充虚函数表中的各入口的。所以,Base类使用Base类的虚函数地址来填充自己的虚函数表,当Drive类的构造函数执行它的时候会创建另外一个虚函数表,并存储自己的虚函数地址。
  现在,你会发现在基类中含有多个虚函数的情况下,派生类并不能完全重写它们。
程序26.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<"ValueatVtable3rdentry="<<(int*)*((int*)*(int*)this+2)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Base::f1"<<endl;}
virtualvoidf2(){cout<<"Base::f2"<<endl;}
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<"ValueatVtable3rdentry="<<(int*)*((int*)*(int*)this+2)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f1"<<endl;}
};

intmain(){
Drived;
return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0E0
ValueatVtable1stentry=004010F0
ValueatVtable2ndentry=00401145
ValueatVtable3rdentry=00000000

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0C8
ValueatVtable1stentry=0040121C
ValueatVtable2ndentry=00401145
ValueatVtable3rdentry=00000000

  这个程序的输出表明基类的虚函数在派生类中并未被重写,然后,派生类的构造函数没有对虚函数的入口做任何的事情。
  那么现在,让我邀请纯虚函数来加入这一游戏,再来看看它的行为吧。请看以下的程序:
程序27.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f1"<<endl;}
virtualvoidf2(){cout<<"Drive::f2"<<endl;}
};

intmain(){
Drived;

return0;
}

  在debug和release模式下,程序的输出有所不同。下面是debug模式的输出:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0BC
ValueatVtable1stentry=00420CB0
ValueatVtable2ndentry=00420CB0

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0A4
ValueatVtable1stentry=00401212
ValueatVtable2ndentry=0040128F

  以下则是release模式的输出:
InBase
VirtualPointer=0012FF80
AddressofVtable=0042115C
ValueatVtable1stentry=0041245D
ValueatVtable2ndentry=0041245D

InDrive
VirtualPointer=0012FF80
AddressofVtable=00421154
ValueatVtable1stentry=00401310
ValueatVtable2ndentry=00401380

  为了更好地弄懂这一原理,我们需要对程序作少许的改动,并尝试使用函数指针来调用虚函数。
程序28.
#include<iostream>
usingnamespacestd;

typedefvoid(*Fun)();

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;

//尝试执行第一个虚函数
FunpFun=(Fun)*((int*)*(int*)this+0);
pFun();

cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"Drive::f1"<<endl;}
virtualvoidf2(){cout<<"Drive::f2"<<endl;}
};

intmain(){
Drived;

return0;
}

  现在程序的行为在debug和release模式下仍然不同。在debug模式下,它会显示一个运行时错误的对话框:

  并且,当你按下“忽略”按钮之后,它会显示下面的对话框:

  而在release模式下运行的话,它只会在控制台窗口中输出错误信息:
InBase
VirtualPointer=0012FF80
AddressofVtable=0042115C
ValueatVtable1stentry=0041245D
ValueatVtable2ndentry=0041245D

runtimeerrorR6025
-purevirtualfunctioncall

  那么这里的R6025是什么?它定义于CMSGS.H头文件中,这个头文件定义了所有CRunTimeLibrary的所有错误信息。
#define_RT_PUREVIRT_TXT"R6025"EOL"-purevirtualfunctioncall"EOL
  事实上,当我们定义了纯虚函数后,编译器就会放置一个名为_purecall的CRunTimeLibrary的函数地址。这个函数定义在PUREVIRT.C之中,它的原型如下:
void__cdecl_purecall(void);//译注:原文此处无分号
  我们可以在程序中直接调用这个函数来达到相同的效果,请看下面这个小程序:
程序29.
intmain(){
_purecall();
return0;
}

  这个程序在debug模式和release模式下的输出和前一个是一样的。为了更好的理解这个问题,让我们把继承链弄得更深一些,并且从Drive类中再继承一个类来看看效果吧。
程序30.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
};

classMostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
virtualvoidf2(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;

return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0D8
ValueatVtable1stentry=00420F40
ValueatVtable2ndentry=00420F40

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0C0
ValueatVtable1stentry=00420F40
ValueatVtable2ndentry=00420F40

InMostDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0A8
ValueatVtable1stentry=00401186
ValueatVtable2ndentry=004010F5

  这个程序表明,Base和Drive类是用相同的值来初始化各自的虚函数表的。那么,如果继承更深一些,并且只有最底层的派生类重写了纯虚函数,在这种情况下又会发生什么呢?这就是在COM程序设计的情况下所发生的了——接口就是只拥有纯虚函数的类,并且一个接口是继承自另一个接口的,只有实现类才会重写接口的纯虚函数。这样一来,每个基类的构造函数就会以相同的值来初始化它们自己的虚函数表入口。所以,这就意味着相同的代码会反复重复下去。
  ATL的主要思想就是让COM组件尽可能的小,但是由于这一行为,接口类的构造函数就会拥有很多不必要的代码。为了解决这一问题,ATL引入了一个宏ATL_NO_VTABLE,它定义在ATLDEF.H中:
#defineATL_NO_VTABLE__declspec(novtable)
  __declspec(novtable)为MicrosoftC++扩展的类属性。它会使编译器不产生初始化虚函数表指针和虚函数表的代码,这样一来就减少了生成代码的尺寸。
  那么,我们来修改一下我们的代码,这样就能更好的明白这一属性究竟能为我们做什么了。
程序31.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

classDrive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
};

class__declspec(novtable)MostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
virtualvoidf2(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;

return0;
}
  程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0CC
ValueatVtable1stentry=00420E60
ValueatVtable2ndentry=00420E60

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0B4
ValueatVtable1stentry=00420E60
ValueatVtable2ndentry=00420E60

InMostDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0B4
ValueatVtable1stentry=00420E60
ValueatVtable2ndentry=00420E60
  这个程序有另外一个结果,也就是Drive和MostDrive类拥有相同的虚函数表地址,但是Base类却不同。事实上,这就是由于我们没有对Base类使用__declspec(novtable)属性的缘故。现在,我们来对继承的Drive类也使用相同的属性,也就是__declspec(novtable)。
程序32.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

class__declspec(novtable)Drive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
};

class__declspec(novtable)MostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
virtualvoidf2(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;

return0;
}
  现在,程序的输出为:
InBase
VirtualPointer=0012FF7C
AddressofVtable=0046C0C0
ValueatVtable1stentry=00420E50
ValueatVtable2ndentry=00420E50

InDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0C0
ValueatVtable1stentry=00420E50
ValueatVtable2ndentry=00420E50

InMostDrive
VirtualPointer=0012FF7C
AddressofVtable=0046C0C0
ValueatVtable1stentry=00420E50
ValueatVtable2ndentry=00420E50

  在MSDN中,对__declspec(novtable)的解释是:它应该使用在纯虚类中。那么,让我们再做一个实验来更好地弄懂这一含义吧。
程序33.
#include<iostream>
usingnamespacestd;

classBase{
public:
Base(){
cout<<"InBase"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
virtualvoidf1()=0;
virtualvoidf2()=0;
};

class__declspec(novtable)Drive:publicBase{
public:
Drive(){
cout<<"InDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;
}
};

class__declspec(novtable)MostDrive:publicDrive{
public:
MostDrive(){
cout<<"InMostDrive"<<endl;
cout<<"VirtualPointer="<<(int*)this<<endl;
cout<<"AddressofVtable="<<(int*)*(int*)this<<endl;
cout<<"ValueatVtable1stentry="<<(int*)*((int*)*(int*)this+0)<<endl;
cout<<"ValueatVtable2ndentry="<<(int*)*((int*)*(int*)this+1)<<endl;
cout<<endl;

//尝试调用第一个虚函数
typedefvoid(*Fun)();
FunpFun=(Fun)*((int*)*(int*)this+0);
pFun();

}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
virtualvoidf2(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;

return0;
}
  我们在程序中添加的新东西是:
//尝试调用第一个虚函数
typedefvoid(*Fun)();
FunpFun=(Fun)*((int*)*(int*)this+0);
pFun();

  并且,当我们运行这个应用程序的时候,我们会面对与前一个程序相同的问题——也就是尝试调用虚函数发生的错误。这就意味着虚函数表并未初始化。MostDrive类并不是一个抽象类,所以我们应该从类中移除__declspec(novtable)。
程序34.
#include<iostream>
usingnamespacestd;

classBase{
public:
virtualvoidf1()=0;
virtualvoidf2()=0;
};

class__declspec(novtable)Drive:publicBase{
};

classMostDrive:publicDrive{
public:
MostDrive(){

//尝试调用第一个虚函数
typedefvoid(*Fun)();
FunpFun=(Fun)*((int*)*(int*)this+0);
pFun();

}
virtualvoidf1(){cout<<"MostDrive::f1"<<endl;}
virtualvoidf2(){cout<<"MostDrive::f2"<<endl;}
};

intmain(){
MostDrived;

return0;
}
  现在,程序就可以正常工作了。它的输出为:
MostDrive::f1

  这个属性并不一定只用在ATL的类中,它可以对任何不能创建对象的类使用。同样,它也并不一定非要用在ATL类中,也就是说它可以从ATL类中省略,不过这就意味着这样会产生更多的代码。
  我希望能在下篇文章中探究更多ATL的秘密。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics