虚拟呼叫使用纯虚拟成员的地址。这合法吗?

时间:2009-06-09 06:48:06

标签: c++ inheritance templates

我读回来(可能是在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 ++。版主并遇到了这个linkhttp://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d)。似乎已经达成共识,因为标准并没有限制它,它是有效的。

EDIT-3:在阅读了codeproject文章后(感谢ovanes),我认为编译器会做一些魔术。由于虚函数是通过vtable(特定于编译器)实现的,因此获取虚函数的地址总是会给出vtable中的偏移量。根据所使用的'this'指针,调用相应的函数(其地址位于偏移量)。我不知道如何证明这一点,因为标准没有说明任何事情!

4 个答案:

答案 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跳转到真正的类方法。