问个问题,怎么实现虚函数机制?
虽然知道vtable,但是像自己实现发现还是有巨大的困难
哪位能帮帮我,网上找到一篇代码 可是运行后代码访问异常,
大家帮我看看吧:
#include <stdio.h>
class Base {
public:
void Output() {
printf("Class Base\n");
}
};
class Derive : public Base {
public:
void Output() {
printf("Class Derive\n");
}
};
void Test(Base *p) {
p->Output();
}
int __cdecl main(int argc, char* argv[])
{
Derive obj;
typedef void (__stdcall *PFUNC)(void*);
void *pThis = &obj;
PFUNC pFunc = (PFUNC)*(unsigned int*)pThis;
pFunc = (PFUNC)*(unsigned int*)pFunc;
return 0;
}
问题点数:20、回复次数:16Top
1 楼cunsh(村少)回复于 2006-03-03 22:11:05 得分 0
class Base {
public:
void Output() {
printf("Class Base\n");
}
virtual ~Base(){} // 加个虚函数.对象里才含有vptr
};Top
2 楼xiaocai0001(高楼目尽欲黄昏/梧桐叶上萧萧雨)回复于 2006-03-03 22:14:30 得分 0
虚函数需要virtual 关键字申明.
有virtual后, 编译器就会对这样的函数建立一个vtable
动态调用时, 按照这个vtable去搜索相应的函数去执行, 这是运行期的动态
Top
3 楼sssa2000()回复于 2006-03-03 22:23:46 得分 0
.......
看来我没说清楚
我的意思是,不使用关键字virtual来实现动态邦定。
所以程序里面用了很多函数指针。Top
4 楼guyanhun(老婆说的都是对的!努力做个好老公!)回复于 2006-03-03 23:12:40 得分 0
编译器会自动为你加一个 vptr ,
然后函数指针加一个偏移地址(就是说当前虚函数所在vtable 中的下标 * 4)运行期得到真正的函数地址。Top
5 楼zenny_chen(ACE Intercessor)回复于 2006-03-03 23:13:51 得分 0
动态绑定是由编译器事前准备的,用户程序无法模拟。
在目标代码中已经有比较清晰的跳转调用。Top
6 楼Jinhao(辣子鸡丁·GAME就这样OVER了)回复于 2006-03-03 23:26:53 得分 10
1, http://blog.csdn.net/jinhao/archive/2004/01/17/4797.aspx
2, http://blog.csdn.net/jinhao/archive/2004/01/17/4799.aspx
3, http://blog.csdn.net/jinhao/archive/2004/01/17/4798.aspx
Top
7 楼lei001(太极)回复于 2006-03-03 23:27:50 得分 0
深入浅出mfc这本书上,有关于虚拟函数的机制介绍的Top
8 楼zzw820626(偶要分,偶要星星)回复于 2006-03-04 13:25:53 得分 0
编译器就会对这样的函数建立一个vtable
Top
9 楼ox_thedarkness()回复于 2006-03-04 14:46:37 得分 10
先来一个简单的例子,为了类型安全用到了模版和成员函数指针。
和虚函数表机制不同之处在于:为了清晰,我没使用单独的虚函数表,而是对每个函数保留一个指针。
class Base{
Base(); // 禁用默认构造函数
void (Base::*_msg)(); // 两个函数指针。第一个输出类的信息
void (Base::*_seti)(int); // 第二个设置_i的属性
protected:
int _i;
// 构造函数为保护,强制要求设置函数指针
template< class T >
Base( void (T::* msg)(), void( T::* seti)(int) )
:_msg( (void(Base::*)()) msg ),_seti( (void(Base::*)(int)) seti ),_i(0){}
public:
// 公开接口,通过虚函数指针调用虚函数
void msg(){ (this->*_msg)(); }
void seti( int i ){ (this->*_seti)( i ); }
};
//=========================================
//基类写法很复杂,但是派生类就很容易写了:
class A: public Base{
public:
A(): Base( &A::msg, &A::seti ){};
void msg(){ cout<<"A, #"<< _i <<endl; }
void seti( int i ){ _i = 2* i; }
};
int main(){
A a;
a.seti( 10 );
a.msg();
}Top
10 楼ox_thedarkness()回复于 2006-03-04 16:42:51 得分 0
上面的代码问题在于:
1 每个虚函数需要一个单独的指针,假如虚函数过多,类的体积就太大。同时也有冗余。
2 虚析构函数问题。 当对基类指针调用 delete, 必须调用派生类的析构函数。
3 只能用于单继承,所有基类偏移都是0。 假如多继承,则基类的this和派生类this不相同。
假如用于多继承,每个函数都必须有修正为正确的偏移。
4 多继承时释放问题。此时传递给 operator delete 的指针可能是错误的,必须修正偏移。
楼主有兴趣可以研究下上面四个问题的手动模拟。前两个比较好办,后两个则过于麻烦。下面解释下我的思路:
解决方案分析如下:
问题1
基类使用一个虚函数表结构指针VPTR。 每个派生类提供一个静态虚函数表VTABLE,构造函数中设置VPTR为自己的虚函数表VTABLE。
问题2
设置一个自定义虚函数完成析构工作。在基类的析构函数中调用这个虚函数 —— 注意:派生类的析构函数则什么都不要做!! 很简单,不同于C++真正的虚析构函数,我们只是模拟调用。 派生类析构时会自动调用基类的析构函数。
前两个问题不难实现。但是后面的问题,涉及到RTTI。需要对派生类大量手动簿记编码工作,实在是容易出错而且划不来。既然C++提供了虚函数和RTTI,那么我们没必要手动完成他们。 不过这里还是提出手动解决方案:
问题3
在自定义虚函数表中提供类型信息,表示每个虚函数的实际偏移。这个挺难封装的。
问题4
重载基类的 operator delete,并且配对重载 operator new(安全性考虑)。读取自定义虚函数表中对象描述部分,正确计算偏移并且删除。
很有趣的是,C++ 中这对咚咚居然能够自动继承... 顺便说一句,在C++本身中也是一样,必须打开RTTI才能正确删除多继承时基类指针。Top
11 楼ox_thedarkness()回复于 2006-03-04 17:14:25 得分 0
口胡, 下面是虚函数表的实现。
解决了上面的问题1 、 2,但是没解决多继承问题。 呵哈... 蛮好玩的。
class Base{
protected:
template< class T >
struct VTABLE{
void (T::*_msg)(); // 两个函数指针。第一个输出类的信息
void (T::*_seti)(int); // 第二个设置_i的属性
void (T::*_delBase)(); // 析构函数
};
private:
Base(); // 禁用默认构造函数
const VTABLE<Base> *_vbptr; //_vbptr,名称"_vptr"在DevC++下与隐藏成员冲突..
protected:
int _i;
// 构造函数为保护,强制要求设置函数指针
template< class T >
Base( VTABLE<T>* vp ): _vbptr( ( VTABLE<Base>* )vp ),_i(0){}
public:
// 公开接口,通过虚函数指针调用虚函数
void msg(){
if( _vbptr && _vbptr->_msg) (this->*_vbptr->_msg)();
}
void seti( int i ){
if( _vbptr && _vbptr->_seti) (this->*_vbptr->_seti)(i);
}
~Base(){
if( _vbptr && _vbptr->_delBase) (this->*_vbptr->_delBase)();
}
};
class A:public Base{
static VTABLE<A> vtable;
void msg(){ cout<<"A, #"<< _i <<endl; }
void seti( int i ){ _i = 2* i; }
void delBase(){ cout<<"~A()"<<endl; }
public:
A(): Base( &vtable ){};
};
A::VTABLE<A> A::vtable = { &A::msg, &A::seti, &A::delBase };
//========================================
// 测试用函数
void testBase( Base& b ){
b.seti( 10 );
b.msg();
}
void delBase ( Base* pb ){
delete pb;
}
int main(){
// 测试1: 派生类和自动析构
{
A a;
testBase( a );
}
cout<<"--------------------------"<<endl;
// 测试2: 基类指针 delete
{
A* pa = new A();
delBase( pa );
}
}
Top
12 楼ugg(逸学堂(exuetang.net))回复于 2006-03-04 21:48:58 得分 0
more effective C++有一节讲解编程实现虚函数机制。
Top
13 楼sssa2000()回复于 2006-03-09 23:21:20 得分 0
楼上各位的解答都十分精彩阿,给100分都不够啊Top
14 楼ox_thedarkness()回复于 2006-03-09 23:45:23 得分 0
//很有趣的是,C++ 中这对咚咚居然能够自动继承... 顺便说一句,在C++本身中也是一样,必须打开RTTI才能正确删除多继承时基类指针。
楼主顶上来了阿。 那就更正一下,C++ RTTI 和 删除多继承时基类指针无关。 多继承时,非最左基类的析构函数必须是虚函数数,才能正确删除基类指针,避免错位问题。Top
15 楼pankun(剑神一笑 Console下面干革命)回复于 2006-03-10 00:07:42 得分 0
所以我建议程序员一定要掌握汇编呢.了解了汇编这些都不是问题.自己看一下反汇编中,怎么调用虚函数的就明白了.
一般就这样的形式
mov eax,类指针
mov edx,[eax] ; 取得VMT表地址
mov ecx,类地址 ; 隐式的传递类指针给类成员函数
call dword ptr [edx+4] ; 调用VMT表中的第二个虚函数Top
16 楼sssa2000()回复于 2006-03-11 22:49:14 得分 0
汇编看过,
gcc输出的和vc输出的都看过了
就是想自己实现一下,看到以上几位的回帖
已经搞定了,谢谢大家Top




