请教一个通过定义一个类的成员函数指针类型,通过虚函数的地址来调用到具体的实例的问题

liukunyewudi 2012-05-22 11:03:19
最近写了怎么一个程序,看起来是没有问题,可以通过虚函数的地址来调用到具体的实例,但是就是有点不太明白?代码如下:
class A
{
public:
typedef void(A::*FUNC)();
public:
virtual void fun1()
{
cout<<"Afun1";
}
virtual void fun2(){}
void Reg()
{
m_FunMgr.insert(map<int, FUNC>::value_type (1,&A::fun1));
m_FunMgr.insert(map<int, FUNC>::value_type (2,&A::fun2));
}
FUNC FindFun(int i)
{
map<int, FUNC>::iterator iter;
iter = m_FunMgr.find(i);
FUNC pFun = (FUNC)iter->second;
return pFun;
}

private:
map<int,FUNC> m_FunMgr;
};

class B :public A
{
public:
void fun1()
{cout<<"BFUN1";}
void fun2()
{cout<<"BFUN2";}

};

int main()
{
A *a = new B;
a->Reg();
A::FUNC fun;
fun = a->FindFun(1);
(a->*(fun))(); //调用的是B的func 这里为啥调用的是子类的
return 0;
}
其实在这里里面存放的虚函数的地址好像是基类的地址,而且这个地址一直都没有 变过。
难道这里存放的不是虚函数的地址,而是一个索引?根据这个索引找到相关的实例指针?
这个真的是不明白了,请大侠指教,多谢~
...全文
194 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
taodm 2012-06-11
  • 打赏
  • 举报
回复
就知道穷试,不肯去找本正规教材或C++标准翻翻。
liukunyewudi 2012-06-11
  • 打赏
  • 举报
回复
通过大家的讨论,发现这个还真的是不太安全的,虽然暂时没有什么问题。。还是怕以后突然会出问题。。我还是不用了吧。多谢大家。。
chundiaosi 2012-05-31
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 4.4 指向member function的指针

指向member函数的指针可能各个编译器的实现不一样,但是基本是通过union来实现的。 既可以保持内存地址,也可以保持函数在虚函数表中的偏移量。


int main()
{
A *a = new B;
A::FUNC fun;
fun = a->getFun();

printf("fun: %p\n");
printf("Afun1: %p\n", &A::fun1);
printf("Afun2: %p\n", &A::fun2);
printf("Bfun1: %p\n", &B::fun1);
printf("Bfun2: %p\n", &B::fun2);
printf("Cfun1: %p\n", &C::cfun1);
printf("Cfun2: %p\n", &C::cfun2);
(a->*(fun))();
//sleep(20);
return 0;
}

输出结果:

===getFun Afun1 addr 0x1
fun: 0x804b00c
Afun1: 0x1
Afun2: 0x5
Bfun1: 0x9
Bfun2: 0xd
Cfun1: 0x1
Cfun2: 0x5
B fun1
==================
在getFun内的值还是一个索引,但是通过a->之后变为内存地址。应该是在返回索引后,用该索引在a指向的对象的虚函数表中查找到了Bfun1,因为vptr指向的是B的虚函数表。

我这里是多继承的情况,即时Afun1与Bfun1的索引不一样,编译器通过对象的偏移还是能调到Bfun1.
刚看完第四章 Function语义学。理解的不一定准确。希望对楼主有帮助,推荐看下。
chundiaosi 2012-05-31
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 4.4 指向member function的指针

指向member函数的指针可能各个编译器的实现不一样,但是基本是通过union来实现的。 既可以保持内存地址,也可以保持函数在虚函数表中的偏移量。


int main()
{
A *a = new B;
A::FUNC fun;
fun = a->getFun();

printf("fun: %p\n");
printf("Afun1: %p\n", &A::fun1);
printf("Afun2: %p\n", &A::fun2);
printf("Bfun1: %p\n", &B::fun1);
printf("Bfun2: %p\n", &B::fun2);
printf("Cfun1: %p\n", &C::cfun1);
printf("Cfun2: %p\n", &C::cfun2);
(a->*(fun))();
//sleep(20);
return 0;
}

输出结果:

===getFun Afun1 addr 0x1
fun: 0x804b00c
Afun1: 0x1
Afun2: 0x5
Bfun1: 0x9
Bfun2: 0xd
Cfun1: 0x1
Cfun2: 0x5
B fun1
==================
在getFun内的值还是一个索引,但是通过a->之后变为内存地址。应该是在返回索引后,用该索引在a指向的对象的虚函数表中查找到了Bfun1,因为vptr指向的是B的虚函数表。

我这里是多继承的情况,即时Afun1与Bfun1的索引不一样,编译器通过对象的偏移还是能调到Bfun1.
刚看完第四章 Function语义学。理解的不一定准确。希望对楼主有帮助,推荐看下。
chundiaosi 2012-05-31
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 4.4 指向member function的指针

指向member函数的指针可能各个编译器的实现不一样,但是基本是通过union来实现的。 既可以保持内存地址,也可以保持函数在虚函数表中的偏移量。


int main()
{
A *a = new B;
A::FUNC fun;
fun = a->getFun();

printf("fun: %p\n");
printf("Afun1: %p\n", &A::fun1);
printf("Afun2: %p\n", &A::fun2);
printf("Bfun1: %p\n", &B::fun1);
printf("Bfun2: %p\n", &B::fun2);
printf("Cfun1: %p\n", &C::cfun1);
printf("Cfun2: %p\n", &C::cfun2);
(a->*(fun))();
//sleep(20);
return 0;
}

输出结果:

===getFun Afun1 addr 0x1
fun: 0x804b00c
Afun1: 0x1
Afun2: 0x5
Bfun1: 0x9
Bfun2: 0xd
Cfun1: 0x1
Cfun2: 0x5
B fun1
==================
在getFun内的值还是一个索引,但是通过a->之后变为内存地址。应该是在返回索引后,用该索引在a指向的对象的虚函数表中查找到了Bfun1,因为vptr指向的是B的虚函数表。

我这里是多继承的情况,即时Afun1与Bfun1的索引不一样,编译器通过对象的偏移还是能调到Bfun1.
刚看完第四章 Function语义学。理解的不一定准确。希望对楼主有帮助,推荐看下。
pathuang68 2012-05-31
  • 打赏
  • 举报
回复
cryingbee 2012-05-30
  • 打赏
  • 举报
回复
我的建议还是是不要用指向虚函数的函数指针,因为不同的编译器可能有不同的处理。

但如果你一定要这样用。。。不过我也查了一下,至少微软的VC下是可以的,基类虚函数指针会调到派生类的虚函数的。

http://msdn.microsoft.com/en-us/library/fa0207h3(v=vs.80).aspx

当你调虚函数时a->func(),会有一段静态的代码,找到func在当前对象(可能是派生类,也可能是基类)虚表中的偏移,然后调用虚表中的函数。这段代码的地址就是&A::func,不论你是基类还是派生类,这段代码都能帮你调到最终的虚函数。
CNXUAN 2012-05-29
  • 打赏
  • 举报
回复
你这样当然是直接调用的A中的fun啊 这是直接通过函数名来调用的并没有通过虚函数表调用 但此时你的B的对象中的虚函数表中 依然是存的 B中fun1的地址啊·[Quote=引用 10 楼 的回复:]
这个一般的对虚函数的处理是这样的,但是对于这个,我调用存放的是A::fun1 这个按照在外面调用来说,要是直接调用这样写的话,不就是直接调用基类的函数吗?

引用 7 楼 的回复:

正解是这样的··当你的类里面出现虚函数的时候 创建对象时就会产生一个虚函数表 对象的首地址存放一个指向这个表的指针 然后虚函数首地址按申明顺序依次存放在这个表中 当子类继承了这个基类并且实现了些虚函数 在创建……
[/Quote]
liukunyewudi 2012-05-28
  • 打赏
  • 举报
回复
大侠能够说的更详细点吗?多谢~[Quote=引用 1 楼 的回复:]

成员函数指针本来就同样能体现多态性。
实现中一般也的确不放地址(要是放地址就够了sizeof也就不会这么诡异了)。
[/Quote]
liukunyewudi 2012-05-28
  • 打赏
  • 举报
回复
这个一般的对虚函数的处理是这样的,但是对于这个,我调用存放的是A::fun1 这个按照在外面调用来说,要是直接调用这样写的话,不就是直接调用基类的函数吗?
[Quote=引用 7 楼 的回复:]

正解是这样的··当你的类里面出现虚函数的时候 创建对象时就会产生一个虚函数表 对象的首地址存放一个指向这个表的指针 然后虚函数首地址按申明顺序依次存放在这个表中 当子类继承了这个基类并且实现了些虚函数 在创建对象的时候 依然会有这个表 并且会用子类中这些函数的地址覆盖基类中相同函数的地址 所以当你调用的时候 调到的是 子类中的函数··
要弄清楚这个问题 跟着内存走就错不了 ····希望……
[/Quote]
liukunyewudi 2012-05-28
  • 打赏
  • 举报
回复
这里面,其实,我还有一个就是非虚的类,也是用的这种处理的方法,奇怪的是,在vc里面虚和非虚都是没有问题的,在X86上,非虚的却出了问题,就是那个this指针指错了。。。
cryingbee 2012-05-23
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]

就是看这样做的有没有风险,目前在一个项目里面为了减少重复代码,用了这个成员函数的指针类型,目前在vc上和X86上是还没有看到什么问题。。
[/Quote]


我知道你想干什么了,我假设你想调的是派生类的函数。个人感觉你这样做还是挺危险的,因为你不知道新版本的编译器会怎么处理。如果你的基类的成员函数不多(比如少于10个),还不如把FindFunc放在基类里:

virtual void FindAndCallFunc(int n);

void A::FindAndCallFunc(int n)
{
switch(n)
{
case 1: func1(); break;
case 2: func2(); break;
// ...
default:
}
}

如果你的函数比较多,可以在基类里加一层非virtual的,比如:
class Base
{
public:
virtual void func1() {printf ("Base::func1\n");}
virtual void func2() {printf ("Base::func2\n");}

// 这里加一层非virtual的函数,保证接下去可以调用到virtual的。
void callFunc1() {func1();}
void callFunc2() {func2();}

virtual void callFuncN(int n)
{
(this->*sFuncTable[n-1])();
}

private:

// 我这里没有用动态添加,为了方便,就用了静态数组
typedef void (Base::*MyFuncType) ();
static MyFuncType sFuncTable [100];
};

Base::MyFuncType Base::sFuncTable [100] = {&Base::callFunc1, &Base::callFunc2};

class Derived : public Base
{
public:
virtual void func1() {printf ("Derived::func1\n");}
};


int _tmain(int argc, _TCHAR* argv[])
{
Base *b = new Derived;
b->callFuncN(1); // Derived
b->callFuncN(2); // Base
return 0;
}


CNXUAN 2012-05-23
  • 打赏
  • 举报
回复
正解是这样的··当你的类里面出现虚函数的时候 创建对象时就会产生一个虚函数表 对象的首地址存放一个指向这个表的指针 然后虚函数首地址按申明顺序依次存放在这个表中 当子类继承了这个基类并且实现了些虚函数 在创建对象的时候 依然会有这个表 并且会用子类中这些函数的地址覆盖基类中相同函数的地址 所以当你调用的时候 调到的是 子类中的函数··
要弄清楚这个问题 跟着内存走就错不了 ····希望能帮到楼主··
liukunyewudi 2012-05-23
  • 打赏
  • 举报
回复
就是看这样做的有没有风险,目前在一个项目里面为了减少重复代码,用了这个成员函数的指针类型,目前在vc上和X86上是还没有看到什么问题。。
chundiaosi 2012-05-23
  • 打赏
  • 举报
回复
&A::fun1
&A::fun2
&B::fun1
&B::fun2

都只是偏移量,如0x01,0x05.
动态绑定的对象是B的对象,则取的是B对象的内存地址为base,去B的虚函数。。。
帅得不敢出门 2012-05-23
  • 打赏
  • 举报
回复
指向成员函数的指针并非指针
它需要包含一些额外信息,获得this指针等。
cryingbee 2012-05-23
  • 打赏
  • 举报
回复
我感觉这个适合编译器相关的,编译器可能把&A::func当成一个索引(导致你看到的情况)
FrankHB1989 2012-05-23
  • 打赏
  • 举报
回复
成员函数指针本来就同样能体现多态性。
实现中一般也的确不放地址(要是放地址就够了sizeof也就不会这么诡异了)。
qq120848369 2012-05-23
  • 打赏
  • 举报
回复
别研究了, 一点用处也没有.

64,662

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧