游戏开发论坛

 找回密码
 立即注册
搜索
查看: 3149|回复: 4

virtual函数调用时的this指针转型问题

[复制链接]

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
发表于 2008-5-5 22:44:00 | 显示全部楼层 |阅读模式
-----------------------------------------------------------
引言 1

以前发过什么“回调动态成员”的帖子中提到,
一个多继承的类 的成员函数指针,无法类型转换。
最后我用了union_cast解决。

其实当时还不知道多继承和虚拟继承的指针漂移的问题。

这几个月来,我的RTTI规范几次修改,最近终于固定了下来。

-----------------------------------------------------------
引言 2

今天正好回过头来重新编写Event类。
(改为,不立即call back,而是存入队列,稍候再一次性调用他们,
防止event handler破坏 UI 类库的消息处理机制)

于是不得不zhong视,安全的 "thiscall"

===========================================================

本帖要探讨什么问题?

不探讨CEvent问题,这点我没有疑惑,
CEvent也不需要支持虚函数回调(就是说不支持函数指针的指针)

不探讨RTTI,这我玩的很熟了,再说与本帖也没多少关系。

探讨一下virtual函数调用时的this指针转型。
探讨一下安全的thiscall,以及是否使用RTTI来进行thiscall

===========================================================


先不急着看virtual函数的case,先看下下面的代码。当然也可以skip~
(对下面的代码,我没有疑问,我的疑问在后面~~)

class B2
{
  int b;
public:
  void fb(){ b=0; cout<<b<<"  this="<<this<<"  B1::fb\n"; }
};
class B1{};

class Cls: public B1, virtual public B2
{
  int m;
public:
  void func(int a,int b){ m=a; cout<<m<<"  "<<b<<"  "<<"  this="<<this<<"  Cls::func\n"; }
};

void main()
{
        Cls *p=new Cls;

//        (((__obj *)p)->*union_cast<void (__obj::*)(void)>(B2::fb))  ( );

//----上面的调用,因为this不正确,所以类开头的信息,vbase表被搞坏了------------

        (((__obj *)p)->*union_cast<void (__obj::*)(int,int)>(Cls::func))  (1, (1, 2) );
        // 当指针漂移存在的时候,C++禁止成员函数的类型转换。包括重新解释转换

        p->func(3,4);
        p->fb();
//************************************************************

这个调用:(((__obj *)p)->*union_cast<void (__obj::*)(void)>(B2::fb))  ( );
是错误的,因为编译器无法 静态地 转换 this 指针,这很简单。道理我就不说了。

-------------------------------------------------------------------

非virtual成员函数调用时,this是静态转型的,从源代码中就可以直接看出来,
应该如何转换this。 当然,除非你违规操作。

下面进入正题...


362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-5-5 23:12:00 | 显示全部楼层

Re:virtual函数调用时的this指针转型问题

p->fb();
这个调用中,C++编译为:
((B2 *)p) -> fb();
因为编译器发现 fb 是基类 B2 中的函数。


然而,让我们考虑一个virtual函数的case:

class B2
{
public:
  virtual void f(){...}
};
class B1{};
class Cls: public B1, virtual public B2
{
public:
  virtual void f(){...}
};

B2 *p = new Cls;
p->f();

这个代码运行到不会有异常,但是,
C++如何处理这种机制呢?

这里面,实际上对this指针进行了动态的 down cast,

不仅如此,virtual基类是无法down cast的!
Cls *pcls = (Cls *)p; // 不允许
(这倒无所谓,我用自定义的RTTI解决的)


关键问题是, this指针的运行时cast是怎么进行的?
很多多态,v-table的资料都没有涉及到这个问题!

p -> f ()

这个过程,不仅仅是:
( * p->vtbl->f ) ( p )

实际上,我们忽略了的问题是,this指针的转型,
即实际上,*动态地*执行了:
( * p->vtbl->f ) ( ( 函数 f 所在的类 * ) p )

-----------------------------------------------------------
那么,再vtable中,记录了cast信息吗?还有这里还涉及到vbase表
-----------------------------------------------------------

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-5-5 23:31:00 | 显示全部楼层

Re:virtual函数调用时的this指针转型问题

再说下另一个问题,这个问题到不是很大。

如何设计我们的thiscall?或者说,功能支持到什么程度?

1) 只支持 this 指针与 回调成员函数 都属于同一个类的情况。

比如看1楼最初的代码:
AddEventHandler( p, B2::fb );
这不允许!(实际上也没有必要这么做!!)

如果想这么做:
AddEventHandler( (B2 *)p, B2::fb );
没有问题~~


然而通常不会用上面这些写法,事件处理函数,一般在类的构造函数中添加:
Cls::Cls()
{
  XXX -> AddEventHandler( this, &B2::fb );
}
这就错了。(当然一般我们也不需要这么做)

不过或许,会在基类 B2 的构造函数中这么做:
B2::B2()
{
  XXX -> AddEventHandler( this, &B2::fb );
}
这就没有问题了~~

再回到 Cls 上来,假设你又写了一个函数 newfunc,然后:
Cls::newfunc(){...}
Cls::Cls()
{
  XXX -> AddEventHandler( this, &Cls::newfunc );
}
这样也没有问题。

不过需要稍微注意下,XXX事件现在有两个Handler。
就是说,XXX事件发生的时候,会调用 fb 和 newfunc.

还有需要注意,“ &Cls::fb ” 也是可以通过编译的哦!
不过,fb 实际上是继承来的 B2 中的函数,这点需要注意。


注意了以上几点,貌似也就没有多少问题了。
而且实际上,处理事件的类,一般不会再被继承了。错误被最小化了~~

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-5-6 00:24:00 | 显示全部楼层

Re:virtual函数调用时的this指针转型问题

2) 尝试一种更好的,支持地更广的方法。(实际用途不大)

RTTI机制会不会给我们带来某些便利?
比如,父类this指针,子类函数指针。

我现行的RTTI是这样的:
class IDynamic
{
public:
        virtual ~IDynamic(){}
        virtual void *Addr()=0;
        virtual const void *Addr()const=0;
        virtual void *Cast(const CStr &name)=0;
        virtual const void *Cast(const CStr &name)const=0;
};
还有一些Is, IsNot函数,和一些#define就不贴了。


实际上,目前这个RTTI不能满足我们的这种call back需求。
所以这个无聊问题的讨论就可以到此为止了。


或许,可以这么做,但是相当无聊,脑残,十分繁琐(而且仍然不是万能的):

typedef (__obj:: *FUNC_ADDR)(); //实际上随便typedef一个即可。
void *CastByFunc(FUNC_ADDR func)=0; // IDynamic中的

比如,现在我需要回调(假如类是从IDynamic继承的):
pThis对象的 pFunc方法。
然后我可以:
void *pRightThis = pThis -> CastByFunc( pFunc );
// 根据方法地址,取得方法的正确的this指针

( reinterpret_cast<__obj *>(pRightThis) ->* union_cast<void (__obj:: *)(...)>(pFunc) ) (...);
//传入正确的this指针到pFunc中去

这么一来,每个类都要实现无聊的精神病似的 CastByFunc。。。
这有点像IDispath的Invoke。
而且,他仍然不能很好解决virtual函数回调的问题(实现多态的回调,即函数指针的指针)


为了一个回调,没有人需要支持这个。
而且,一般情况,事件Handler,他的位置是这样的:

class FormABC : public Form
{
    void Button1_Click(...);
};
一般来说,不需要在去继承FormABC了,FormABC本身就是对 Form 的 子类化。
而基类Form,作为事件的产生者,不需要call back。

-----------------------------------------------------------------------
或许,可以学学java...用内部类和virtual,来实现callback。这样弹性很强。
当然,从原理上讲,virtual仍然是函数指针,而且限制比指针多。

那么,为什么他功能很强呢?很简单,经过 2 次调用,所以功能强!

我突然觉得找到了一点灵感。。。

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-5-6 00:47:00 | 显示全部楼层

Re:virtual函数调用时的this指针转型问题

回调virtual函数的时候保持多态,也就是函数指针的指针,这看起来很不容易。
但我们看看java为什么能够实现的。

本质上讲,java中的回调,就算你回调virtual函数,这里也不存在任何 2 级函数指针。
但是,存在着 2 个函数指针。

首先是,实现事件监听器的内部类,他的vtable中有1个函数指针,
然后,这个内部类中实现的方法,又去调外部类的virtual函数,即另外一个函数指针。

即:

函数指针1 -> 中介函数 -> 函数指针2 -> 真正想调用的函数

通过两个 1 级函数指针,可以实现 函数指针的指针。


这里有2个阶段,我们先分析第1阶段。


比较一下java的回调(多态)和我们的回调(违规操作),本质都是this指针 + 函数指针。
不过安全性不一样。安全性体现在何处??---- this指针的正确类型转换。

前面说过了,在调用virtual函数的时候,this指针不会出故障。比如:
interface_ptr -> MyVirtualFunc ();
不用管他,总之this指针会被自动转为正确的类型。
java里面使用virtual机制来实现回调,所以总是安全的。

相反,前面也说了,我们“违规操作”强行实现回调,是不安全的,
比如,this指针 一定要和 function所在的类 匹配!


好了,第1阶段姑且就说到这里,总之不管安全与否,本质1样。


下面看第2阶段,

这里有个疑问,“需要第2阶段??”
java需要,比如,你在窗体中处理不同按钮的事件,你需要一个内部类来进行第2次调用。
C++里我不需要!第1阶段就可以搞定!(当然不是100%安全)

除非,你需要回调一个virtual函数,这种情况下必须要用 2 个阶段...
不过这种情况一般用不到。。。orz


现在有一个信仰的问题了:

java的方法,和我的“违规操作”方法,哪个好?

一般情况下,我的方法好,因为只要一次调用就可以搞定,没有罗嗦的内部类。
除非,极端情况,你非要回调virtual函数。那么2终方式差不多,而java安全些。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-22 14:46

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表