读书人

类之间强制转换后虚函数的调用有关问题

发布时间: 2012-04-27 11:57:44 作者: rapoo

类之间强制转换后虚函数的调用问题,不知道怎么描述,进来细看
INonDelegatingUnknown接口与IUnknow接口
定义基本类似 定义如下

C/C++ code
interface   IUnknown   {        virtual   HRESULT QueryInterface(REFIID   iid,void **ppv);        virtual   ULONG   AddRef(void);        virtual   ULONG   Release(void);   };interface INonDelegatingUnknown{    virtual HRESULT NonDelegatingQueryInterface (REFIID riid, LPVOID *ppv) ;    virtual ULONG NonDelegatingAddRef(void) ;    virtual ULONG NonDelegatingRelease(void) ;};

这样两个函数定义都很相似的两个类可以不可以理解成:
虚函数定义完全相同的两个类,其对象的虚函数表结构也完全相同?

然后问题来了
我定义了这样两个类
C/C++ code
class A{public:    virtual    void method1()    {        cout << "This is A`s method1()"<<endl;    }    virtual void method2()    {        cout << "This is A`s method2()"<<endl;    }    virtual void method3()    {        cout << "This is A`s method3()"<<endl;    }};class CallA{public:    virtual    void callMethod1() = 0;    virtual    void callMethod2() = 0;    virtual    void callMethod3() = 0;};

现在我通过实例化一个A 并且创建一个CallA指针
通过强制转换 将A转化为一个CallA指针

C/C++ code
    A    testA;    CallA *pCallA = (CallA *)(&testA);    


然后我通过pCallA调用自身并不存在的方法。可以打到调用A中已经实现好的方法的目的
C/C++ code
pCallA->callMethod1();


请问为什么会有这种使用方法?应用在什么场合?为什么要这样用。

我实在看和COM有关的文档里看到这样一句代码联想到的这个
C/C++ code
CMyComponent::CMyComponent(IUnknown *pOuterUnkown){    if (pOuterUnknown == NULL)       [color=#FF0000] m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;[/color]    else        m_pUnknown = pOuterUnknown;    [ ... more constructor code ... ]}


红色部分
CMyComponent继承了INonDelegatingUnknown 接口
将this指针通过两级转换 使this指针先指向CMyComponent内的INonDelegatingUnknown 部分
再转换为IUnknown类型。
具体调用的时候
通过m_pUnknown -> QueryInterface()
可能调用到INonDelegatingUnknown内的NonDelegatingQueryInterface方法
也可能调用到IUnknown内的QueryInterface方法




[解决办法]
1.虚函数表是属于类的,每个类都有一个虚函数表
只要是不同的类,哪怕类里面的虚函数标志完全相同,也是属于不同的虚函数表,编译后被解析为不同类的虚函数,参考深度探索C++对象模型 函数语意学
2.指针转型只是影响 被指出的内存的大小和其内容
CallA *pCallA = (CallA *)(&testA);
拿你这句来说,CallA *pCallA 编译时期就确定了要指出的内存大小(内容不确定)
(CallA *)(&testA); 这个转型的作用是改变&testA这个指针所指出的内存大小,改为pCallA需要指出的内存大小
正个语句的意思就是,将首地址赋给pCallA ,在&testA指针指出的内存上扩充或者切割,以适应pCallA 所需要指出的内存大小
事实上pCallA->callMethod1();
这种调用没有定义
[解决办法]
首先 你这么强制转换是错误的 类之间的强制转换只保证将子类的指针强制转换成基类 时的正确性
其他所有的自定义类之间的强制转换都不保证其正确性 行为视编译器而定
规则就在那里 为啥都喜欢违反规则呢?
另外你上面的问题建议去仔细的看下 虚函数表的实现
有2本书里有详细说明 深度探索C++面向对象模型 和 C++设计与演化
[解决办法]
探讨
这个我没有是试过,不过我分析dll里虚表记录方法偏移量(虚地址+偏移就是真正的方法地址),以前我们将虚表删除,防止别人调用直接通过编译量调用方法,而你两个类里,方法类型和参数都是一样的,也就是说偏移地址可能相同,所以才会产生你的问题瓦?

具体还请高手讨论

[解决办法]
探讨
只因为CallA *pCallA = (CallA *)(&testA);
这句话过后CallA类型下的虚函数表指针,指向了A的虚函数表。


所以当你调用你的第一个到第三个CallA的虚函数时,跳转的函数执行地址会分别对应于
A虚函数表中前3个以4字节划分的地址(64位的就是8字节划分)。


[解决办法]
这种问题真是蛋疼啦
在栈结构上
CallA *pCallA = (CallA *)(&testA);
这个语句就是个模型替换,这个替换分扩充替换和切割替换,决定于替换对象模型与被替换模型的大小
这种替换没有意义,替换后,内存空间里还残留着被替换对象的数据值,数据按字节以及声明次序依次占据空间。结果导致CallA 类的vptr的值是原来对象testA的vptr的值,这个指针指向的是A类虚表地址,更要命的是:编译器寻址虚函数是很笨的,在编译的时候只记住了每个虚函数的索引,比如说callMethod3();他只会机械的去索引为2的位置,然后把函数解析出来。
这个时候调用虚函数
C/C++ code
|-------|<<--pCallA  A类的虚函数表|vptr --|  ------->>  |------||data1  |            0| vf1  ||data2  |            1| vf2  ||data3  |            2| vf3  | |....   |             |------||-------|| 

读书人网 >C++

热点推荐