我读回来(可能是在c.l.c ++。版主),虚拟函数调用可以被模板化。我尝试过以下几行。
#include <iostream>
template<class T, class FUN>
void callVirtual(T& t, FUN f){
(*t.*f)();
}
struct Base{
virtual ~Base(){}
virtual void sayHi()=0;
};
struct Derived : public Base{
void sayHi(){
std::cout << "Hi!" << std::endl;
}
};
void Test(){
Base* ptr = new Derived;
callVirtual(ptr,&Base::sayHi);
}
int main()
{
Test();
return 0;
}
Output:
Hi!
虽然在编译时给定纯虚基本成员方法的地址,但是在运行时调用正确的方法。 在标准C ++中获取纯虚拟成员的地址是否合法?
提前致谢
EDIT-1:我删除了问题的第二部分'它是如何工作的?'。看起来这就是引起人们注意的事情。
EDIT-2:我搜索了c.l.c ++。版主并遇到了这个link(http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d)。似乎已经达成共识,因为标准并没有限制它,它是有效的。
EDIT-3:在阅读了codeproject文章后(感谢ovanes),我认为编译器会做一些魔术。由于虚函数是通过vtable(特定于编译器)实现的,因此获取虚函数的地址总是会给出vtable中的偏移量。根据所使用的'this'指针,调用相应的函数(其地址位于偏移量)。我不知道如何证明这一点,因为标准没有说明任何事情!
答案 0 :(得分:1)
当然可以。您的代码与仅仅调用纯虚方法没有区别:
void Test()
{
Base* ptr = new Derived;
ptr->sayHi();
delete ptr;
}
唯一的区别是你有另一种机制来进行呼叫,在这种情况下是通过callVirtual()。
答案 1 :(得分:1)
正如Magnus Skog所说,模板部分并不真正相关。它归结为:
(ptr->* &Base::sayHi)()
似乎有效,但
ptr->Base::sayHi()
显然不是因为sayHi是纯粹的虚拟。
我无法在标准中找到任何,但是当你获取虚拟或纯虚函数的地址时会发生什么。我不确定这是否合法。它可以在GCC和MSVC中运行,而且Comeau的在线编译器也不会抱怨。
修改强>
即使它是有效的,正如您的编辑所说,我仍然想知道这意味着什么。
如果我们为了简单起见假设sayHi
是非纯的(因此存在Base::sayHi
的定义),那么如果我取其地址会发生什么?我是否获得了Base :: sayHi的地址,或者vtable指向的函数的地址(在这种情况下是Derived :: sayHi)?
显然,编译器会假设后者,但为什么呢?
在基类中拨打ptr->Base::sayHi()
来电sayHi
,但是取Base::sayHi
的地址会给我Derived::sayHi
的地址
这对我来说似乎不一致。这背后有什么理由让我失踪吗?
答案 2 :(得分:1)
下面的文章广泛讨论了C ++中的成员函数指针,它们是如何实现的以及缺陷在哪里。它还处理虚拟成员函数指针等等。我想它会回答你所有的问题。
http://www.codeproject.com/KB/cpp/FastDelegate.aspx
它还展示了如何在C ++中实现委托以及您可能陷入的陷阱。
有善意的问候,
Ovanes
答案 3 :(得分:0)
我猜它是未定义的。我在规范中找不到任何东西。
使用名为vtable的概念实现虚拟方法。
我想说它是特定的编译器实现。我真的认为这不是一个纯粹的虚拟,如果它只是虚拟的话也会发生同样的情况。
我刚用Visual Studio 2008编译你的代码并解析了exe。 VS2008的作用是创建一个thunk函数,使用传入的'this'指针跳转到vtable条目。
这是对callVirtual模板函数的设置和调用。
push offset j_??_9Base@@$B3AE ; void (__thiscall *)(Base *)
lea eax, [ebp+ptr]
push eax ; Base **
call j_??$callVirtual@PAUBase@@P81@AEXXZ@@YAXAAPAUBase@@P80@AEXXZ@Z ; callVirtual<Base *,void (Base::*)(void)>(Base * &,void (Base::*)(void))
所以它将函数指针传递给thunk函数:j_??_9Base@@$B3AE
; void __thiscall Base___vcall_(Base *)
j_??_9Base@@$B3AE proc near
jmp ??_9Base@@$B3AE ; [thunk]: Base::`vcall'{4,{flat}}
j_??_9Base@@$B3AE endp
所有thunk函数正在使用vtable跳转到真正的类方法。